Module: Morpheus::Cli::SecondaryRestCommand

Included in:
LoadBalancerMonitors, LoadBalancerPools, LoadBalancerProfiles, LoadBalancerVirtualServers, NetworkServerGroups
Defined in:
lib/morpheus/cli/mixins/secondary_rest_command.rb

Overview

SecondaryRestCommand is a mixin for Morpheus::Cli command classes. for resources that are secondary to some parent resource. Provides basic CRUD commands: list, get, add, update, remove The parent resource is specified as the first argument for all the comments.

Example of a SecondaryRestCommand for ‘morpheus load-balancer-virtual-servers`.

class Morpheus::Cli::LoadBalancerVirtualServers

include Morpheus::Cli::CliCommand
include Morpheus::Cli::RestCommand
include Morpheus::Cli::SecondaryRestCommand
include Morpheus::Cli::LoadBalancersHelper

set_command_name :'load-balancer-virtual-servers'
register_subcommands :list, :get, :add, :update, :remove

register_interfaces :load_balancer_virtual_servers,
                    :load_balancers, :load_balancer_types

set_rest_parent_name :load_balancers

end

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object



26
27
28
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 26

def self.included(base)
  base.extend ClassMethods
end

Instance Method Details

#_get(parent_id, id, params, options) ⇒ Object



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 324

def _get(parent_id, id, params, options)
  parent_record = rest_parent_find_by_name_or_id(parent_id)
  if parent_record.nil?
    return 1, "#{rest_parent_label} not found for '#{parent_id}"
  end
  parent_id = parent_record['id']
  if id !~ /\A\d{1,}\Z/
    record = rest_find_by_name_or_id(parent_id, id)
    if record.nil?
      return 1, "#{rest_label} not found for '#{id}"
    end
    id = record['id']
  end
  rest_interface.setopts(options)
  if options[:dry_run]
    print_dry_run rest_interface.dry.get(parent_id, id, params)
    return
  end
  json_response = rest_interface.get(parent_id, id, params)
  render_response_for_get(json_response, options.merge({:parent_record => parent_record}))
  return 0, nil
end

#add(args) ⇒ Object



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
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 367

def add(args)
  parent_id, parent_record = nil, nil
  record_type_id = nil
  options = {:options => {:context_map => rest_option_context_map}}
  option_types = respond_to?("add_#{rest_key}_option_types", true) ? send("add_#{rest_key}_option_types") : []
  advanced_option_types = respond_to?("add_#{rest_key}_advanced_option_types", true) ? send("add_#{rest_key}_advanced_option_types") : []
  type_option_type = option_types.find {|it| it['fieldName'] == 'type'} 
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    if rest_has_name
      opts.banner = subcommand_usage("[#{rest_parent_arg}] [name]")
    else
      opts.banner = subcommand_usage("[#{rest_parent_arg}]")
    end
    if rest_has_type && type_option_type.nil?
      opts.on( '-t', "--#{rest_type_arg} TYPE", "#{rest_type_label}" ) do |val|
        record_type_id = val
      end
    end
    build_option_type_options(opts, options, option_types)
    build_option_type_options(opts, options, advanced_option_types)
    build_standard_add_options(opts, options)
    opts.footer = <<-EOT
Create a new #{rest_label.downcase}.
[#{rest_parent_arg}] is required. This is the #{rest_parent_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
[name] is required. This is the name of the new #{rest_label.downcase}.
EOT
  end
  optparse.parse!(args)
  verify_args!(args:args, optparse:optparse, min:1, max: 2)
  # todo: make supporting args[0] optional and more flexible
  # for now args[0] is assumed to be the 'name'
  record_name = nil
  parent_id = args[0]
  if rest_has_name
    if args[1]
      record_name = args[1]
    end
    verify_args!(args:args, optparse:optparse, min:1, max: 2)
  else
    verify_args!(args:args, optparse:optparse, count: 1)
  end
  connect(options)
  # load parent record
  # todo: prompt instead of error
  parent_record = rest_parent_find_by_name_or_id(parent_id)
  if parent_record.nil?
    return 1, "#{rest_parent_label} not found for '#{parent_id}"
  end
  parent_id = parent_record['id']
  # load or prompt for type
  if rest_has_type && type_option_type.nil?
    if record_type_id.nil?
      #raise_command_error "#{rest_type_label} is required.\n#{optparse}"
      type_list = rest_type_interface.list({max:10000, creatable: true})[rest_type_list_key]
      type_dropdown_options = type_list.collect {|it| {'name' => it['name'], 'value' => it['code']} }
      record_type_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'fieldLabel' => rest_type_label, 'type' => 'select', 'selectOptions' => type_dropdown_options, 'required' => true}], options[:options], @api_client)['type']
    end
    record_type = rest_type_find_by_name_or_id(record_type_id)
    if record_type.nil?
      return 1, "#{rest_type_label} not found for '#{record_type_id}"
    end
  end
  passed_options = parse_passed_options(options)
  options[:params] ||= {}
  options[:params][rest_parent_param] = parent_id
  options[:options]['_object_key'] = rest_object_key
  payload = {}
  if options[:payload]
    payload = options[:payload]
    payload.deep_merge!({rest_object_key => passed_options})
  else
    record_payload = {}
    if record_name
      record_payload['name'] = record_name
      options[:options]['name'] = record_name # injected for prompt
      options[:options][rest_arg] = record_name
    end
    if rest_has_type && record_type
      # record_payload['type'] = {'code' => record_type['code']}
      record_payload['type'] = record_type['code']
      options[:options]['type'] = record_type['code'] # injected for prompt
      # initialize params for loading optionSource data
      options[:params]['type'] = record_type['code']
    end
    record_payload.deep_merge!(passed_options)
    if option_types && !option_types.empty?
      v_prompt = Morpheus::Cli::OptionTypes.prompt(option_types, options[:options], @api_client, options[:params])
      v_prompt.deep_compact!
      v_prompt.booleanize! # 'on' => true
      record_payload.deep_merge!(v_prompt)
    end
    # options by type
    if rest_has_type && record_type.nil?
      type_value = record_payload['type'].is_a?(Hash) ? record_payload['type']['id'] : record_payload['type']
      if type_value
        record_type = rest_type_find_by_name_or_id(type_value)
        if record_type.nil?
          return 1, "#{rest_type_label} not found for '#{type_value}"
        end
      end
      # reload the type by id to get all the details ie. optionTypes
      if record_type && record_type['optionTypes'].nil?
        record_type = rest_type_find_by_name_or_id(record_type['id'])
      end
    end
    if respond_to?("load_option_types_for_#{rest_key}", true)
      my_option_types = send("load_option_types_for_#{rest_key}", record_type, parent_record)
    else
      my_option_types = record_type ? record_type['optionTypes'] : nil
    end
    if my_option_types && !my_option_types.empty?
      # remove redundant fieldContext
      my_option_types.each do |option_type|
        if option_type['fieldContext'] == rest_object_key
          option_type['fieldContext'] = nil
        end
      end
      api_params = (options[:params] || {}).merge(record_payload)
      v_prompt = Morpheus::Cli::OptionTypes.prompt(my_option_types, options[:options], @api_client, api_params)
      v_prompt.deep_compact!
      v_prompt.booleanize! # 'on' => true
      record_payload.deep_merge!(v_prompt)
    end
    # advanced options (uses no_prompt)
    if advanced_option_types && !advanced_option_types.empty?
      v_prompt = Morpheus::Cli::OptionTypes.no_prompt(advanced_option_types, options[:options], @api_client, options[:params])
      v_prompt.deep_compact!
      v_prompt.booleanize! # 'on' => true
      record_payload.deep_merge!(v_prompt)
    end
    if respond_to?("#{rest_key}_add_prompt", true)
      record_payload = send("#{rest_key}_add_prompt", record_payload, record_type, parent_record, options)
    end
    # permissions
    if rest_perms_config[:enabled]
      if rest_perms_config[:version] == 2
        perms = prompt_permissions_v2(options.deep_merge(rest_perms_config[:options] || {}), rest_perms_config[:excludes] || [])
      else
        perms = prompt_permissions(options.deep_merge(rest_perms_config[:options] || {}), rest_perms_config[:excludes] || [])
      end
      unless rest_perms_config[:name].nil?
        perms.transform_keys! {|k| k == 'resourcePermissions' ? rest_perms_config[:name] : k}
      end
      unless rest_perms_config[:context].nil?
        perms_context = {}
        perms_context[rest_perms_config[:context]] = perms
        perms = perms_context
      end
      record_payload.merge!(perms)
    end
    payload[rest_object_key] = record_payload
  end
  rest_interface.setopts(options)
  if options[:dry_run]
    print_dry_run rest_interface.dry.create(parent_id, payload)
    return
  end
  json_response = rest_interface.create(parent_id, payload)
  if json_response['success']
    render_response(json_response, options, rest_object_key) do
      record = json_response[rest_object_key]
      print_green_success "Added #{rest_label.downcase} #{record.nil? ? json_response['id'] : record['name'] || record['id']}"
      return _get(parent_id, record.nil? ? json_response['id'] : record['id'], {}, options)
    end
  else
    print red
    print json_response['msg']
    print "\n"
  end
  return 0, nil
end

#get(args) ⇒ Object



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 305

def get(args)
  params = {}
  options = {}
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage("[#{rest_parent_arg}] [#{rest_arg}]")
    build_get_options(opts, options, params)
    opts.footer = <<-EOT
Get details about #{a_or_an(rest_label)} #{rest_label.downcase}.
[#{rest_parent_arg}] is required. This is the #{rest_parent_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
[#{rest_arg}] is required. This is the #{rest_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_label)} #{rest_label.downcase}.
EOT
  end
  optparse.parse!(args)
  verify_args!(args:args, optparse:optparse, min:2)
  connect(options)
  parse_get_options!(args.count > 1 ? args[1..-1] : [], options, params)
  _get(args[0], args[1..-1].join(" "), params, options)
end

#list(args) ⇒ Object



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 262

def list(args)
  parent_id, parent_record = nil, nil
  params = {}
  options = {}
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage("[#{rest_parent_arg}] [search]")
    build_list_options(opts, options, params)
    opts.footer = <<-EOT
List #{rest_label_plural.downcase}.
[#{rest_parent_arg}] is required. This is the #{rest_parent_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
[search] is optional. This is a search phrase to filter the results.
EOT
  end
  optparse.parse!(args)
  verify_args!(args:args, optparse:optparse, min:1)
  connect(options)
  parent_id = args[0]
  parent_record = rest_parent_find_by_name_or_id(parent_id)
  if parent_record.nil?
    return 1, "#{rest_parent_label} not found for '#{parent_id}"
  end
  parent_id = parent_record['id']
  parse_list_options!(args.count > 1 ? args[1..-1] : [], options, params)
  rest_interface.setopts(options)
  if options[:dry_run]
    print_dry_run rest_interface.dry.list(parent_id, params)
    return
  end
  json_response = rest_interface.list(parent_id, params)
  render_response(json_response, options, rest_list_key) do
    records = json_response[rest_list_key]
    print_h1 "Morpheus #{rest_label_plural}"
    if records.nil? || records.empty?
      print cyan,"No #{rest_label_plural.downcase} found.",reset,"\n"
    else
      print as_pretty_table(records, rest_list_column_definitions(options.merge({:parent_record => parent_record})).upcase_keys!, options)
      print_results_pagination(json_response) if json_response['meta']
    end
    print reset,"\n"
  end
  return 0, nil
end

#registered_interfacesObject



258
259
260
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 258

def registered_interfaces
  self.class.registered_interfaces
end

#remove(args) ⇒ Object



658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 658

def remove(args)
  params = {}
  options = {}
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage("[#{rest_parent_arg}] [#{rest_arg}]")
    build_standard_remove_options(opts, options)
    opts.footer = <<-EOT
Delete an existing #{rest_label.downcase}.
[#{rest_parent_arg}] is required. This is the #{rest_parent_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
[#{rest_arg}] is required. This is the #{rest_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_label)} #{rest_label.downcase}.
EOT
  end
  optparse.parse!(args)
  verify_args!(args:args, optparse:optparse, count:2)
  connect(options)
  parent_id = args[0]
  id = args[1]
  parent_record = rest_parent_find_by_name_or_id(parent_id)
  if parent_record.nil?
    return 1, "#{rest_parent_label} not found for '#{parent_id}"
  end
  record = rest_find_by_name_or_id(parent_record['id'], id)
  if record.nil?
    return 1, "#{rest_name} not found for '#{id}'"
  end
  unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the #{rest_label.downcase} #{record['name'] || record['id']}?")
    return 9, "aborted"
  end
  params.merge!(parse_query_options(options))
  rest_interface.setopts(options)
  if options[:dry_run]
    print_dry_run rest_interface.dry.destroy(parent_id, record['id'], params)
    return 0, nil
  end
  json_response = rest_interface.destroy(parent_id, record['id'], params)
  render_response(json_response, options) do
    print_green_success "Removed #{rest_label.downcase} #{record['name'] || record['id']}"
  end
  return 0, nil
end

#render_response_details_for_get(record, options) ⇒ Object



363
364
365
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 363

def render_response_details_for_get(record, options)
  # override for custom details
end

#render_response_for_get(json_response, options) ⇒ Object



347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 347

def render_response_for_get(json_response, options)
  render_response(json_response, options, rest_object_key) do
    record = json_response[rest_object_key]
    print_h1 rest_label, [], options
    print cyan
    print_description_list(rest_column_definitions(options), record, options)
    # show config settings...
    if record['optionTypes'] && record['optionTypes'].size > 0
      print_h2 "Option Types", options
      print format_option_types_table(record['optionTypes'], options, rest_object_key)
    end
    render_response_details_for_get(record, options)
    print reset,"\n"
  end
end

#rest_find_by_name_or_id(parent_id, val) ⇒ Object

override RestCommand method to include parent_id parameter



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 240

def rest_find_by_name_or_id(parent_id, val)
  # use explicitly defined finders
  # else default to new generic CliCommand find_by methods
  if rest_has_name
    if respond_to?("find_#{rest_key}_by_name_or_id", true)
      send("find_#{rest_key}_by_name_or_id", parent_id, val)
    else
      find_by_name_or_id(rest_key, parent_id, val)
    end
  else
    if respond_to?("find_#{rest_key}_by_id", true)
      send("find_#{rest_key}_by_id", parent_id, val)
    else
      find_by_id(rest_key, parent_id, val)
    end
  end
end

#rest_parent_argObject



177
178
179
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 177

def rest_parent_arg
  self.class.rest_parent_arg
end

#rest_parent_column_definitions(options) ⇒ Object



213
214
215
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 213

def rest_parent_column_definitions(options)
  send("#{rest_parent_key}_column_definitions", options)
end

#rest_parent_find_by_name_or_id(val) ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 221

def rest_parent_find_by_name_or_id(val)
  # use explicitly defined finders
  # else default to new generic CliCommand find_by methods
  if rest_parent_has_name
    if respond_to?("find_#{rest_parent_key}_by_name_or_id", true)
      send("find_#{rest_parent_key}_by_name_or_id", val)
    else
      find_by_name_or_id(rest_parent_key, val)
    end
  else
    if respond_to?("find_#{rest_parent_key}_by_id", true)
      send("find_#{rest_parent_key}_by_id", val)
    else
      find_by_id(rest_parent_key, val)
    end
  end
end

#rest_parent_has_nameObject



185
186
187
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 185

def rest_parent_has_name
  self.class.rest_parent_has_name
end

#rest_parent_interfaceObject



201
202
203
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 201

def rest_parent_interface
  instance_variable_get("@#{rest_parent_interface_name}_interface")
end

#rest_parent_interface_nameObject



197
198
199
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 197

def rest_parent_interface_name
  self.class.rest_parent_interface_name # || "@#{rest_parent_name}_interface"
end

#rest_parent_keyObject



173
174
175
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 173

def rest_parent_key
  self.class.rest_parent_key
end

#rest_parent_labelObject



189
190
191
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 189

def rest_parent_label
  self.class.rest_parent_label
end

#rest_parent_label_pluralObject



193
194
195
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 193

def rest_parent_label_plural
  self.class.rest_parent_label_plural
end

#rest_parent_list_column_definitions(options) ⇒ Object



217
218
219
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 217

def rest_parent_list_column_definitions(options)
  send("#{rest_parent_key}_list_column_definitions", options)
end

#rest_parent_list_keyObject



209
210
211
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 209

def rest_parent_list_key
  send("#{rest_parent_key}_list_key")
end

#rest_parent_nameObject

duplicated the rest_* settings with rest_parent, for the parents resource



169
170
171
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 169

def rest_parent_name
  self.class.rest_parent_name
end

#rest_parent_object_keyObject



205
206
207
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 205

def rest_parent_object_key
  send("#{rest_parent_key}_object_key")
end

#rest_parent_paramObject



181
182
183
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 181

def rest_parent_param
  self.class.rest_parent_param
end

#update(args) ⇒ Object



539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
# File 'lib/morpheus/cli/mixins/secondary_rest_command.rb', line 539

def update(args)
  record_type = nil
  record_type_id = nil
  options = {}
  option_types = respond_to?("update_#{rest_key}_option_types", true) ? send("update_#{rest_key}_option_types") : []
  advanced_option_types = respond_to?("update_#{rest_key}_advanced_option_types", true) ? send("update_#{rest_key}_advanced_option_types") : []
  optparse = Morpheus::Cli::OptionParser.new do |opts|
    opts.banner = subcommand_usage("[#{rest_parent_arg}] [#{rest_arg}] [options]")
    build_standard_update_options(opts, options)
    opts.footer = <<-EOT
Update an existing #{rest_label.downcase}.
[#{rest_parent_arg}] is required. This is the #{rest_parent_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
[#{rest_arg}] is required. This is the #{rest_has_name ? 'name or id' : 'id'} of #{a_or_an(rest_label)} #{rest_label.downcase}.
EOT
  end
  optparse.parse!(args)
  verify_args!(args:args, optparse:optparse, count:2)
  connect(options)
  parent_id = args[0]
  id = args[1]
  parent_record = rest_parent_find_by_name_or_id(parent_id)
  if parent_record.nil?
    return 1, "#{rest_parent_label} not found for '#{parent_id}"
  end
  parent_id = parent_record['id']
  connect(options)
  record = rest_find_by_name_or_id(parent_id, id)
  if record.nil?
    return 1, "#{rest_name} not found for '#{id}'"
  end
  # load type so we can prompt for those option types
  if rest_has_type
    record_type_id = record['type']['id']
    record_type = rest_type_find_by_name_or_id(record_type_id)
    if record_type.nil?
      return 1, "#{rest_type_label} not found for '#{record_type_id}"
    end
    # reload the type by id to get all the details ie. optionTypes
    if record_type['optionTypes'].nil?
      record_type = rest_type_find_by_name_or_id(record_type['id'])
    end
  end
  passed_options = parse_passed_options(options)
  payload = {}
  if options[:payload]
    payload = options[:payload]
    payload.deep_merge!({rest_object_key => passed_options}) unless passed_options.empty?
  else
    record_payload = passed_options
    if rest_has_type && record_type
      # inject type to options for prompting
      # record_payload['type'] = record_type['code']
      # options[:options]['type'] = record_type['code']
      # initialize params for loading optionSource data
      options[:params] ||= {}
      options[:params]['type'] = record_type['code']
    end
    # update options without prompting by default
    if false && option_types && !option_types.empty?
      api_params = (options[:params] || {}).merge(record_payload) # need to merge in values from record too, ughhh
      v_prompt = Morpheus::Cli::OptionTypes.no_prompt(option_types, options[:options], @api_client, api_params)
      v_prompt.deep_compact!
      v_prompt.booleanize! # 'on' => true
      record_payload.deep_merge!(v_prompt)
    end
    # options by type
    my_option_types = nil
    if respond_to?("load_option_types_for_#{rest_key}", true)
      my_option_types = send("load_option_types_for_#{rest_key}", record_type, parent_record)
    else
      my_option_types = record_type ? record_type['optionTypes'] : nil
    end
    if false && my_option_types && !my_option_types.empty?
      # remove redundant fieldContext
      # make them optional for updates
      # todo: use current value as default instead of just making things optioanl
      # maybe new prompt() options like {:mode => :edit, :object => storage_server} or something
      my_option_types.each do |option_type| 
        if option_type['fieldContext'] == rest_object_key
          option_type['fieldContext'] = nil
        end
        option_type.delete('required')
        option_type.delete('defaultValue')
      end
      v_prompt = Morpheus::Cli::OptionTypes.no_prompt(my_option_types, options[:options], @api_client, options[:params])
      v_prompt.deep_compact!
      v_prompt.booleanize! # 'on' => true
      record_payload.deep_merge!(v_prompt)
    end
    # advanced options
    if false && advanced_option_types && !advanced_option_types.empty?
      v_prompt = Morpheus::Cli::OptionTypes.no_prompt(advanced_option_types, options[:options], @api_client, options[:params])
      v_prompt.deep_compact!
      v_prompt.booleanize! # 'on' => true
      record_payload.deep_merge!(v_prompt)
    end
    # remove empty config, compact could hanlde this
    if record_payload['config'] && record_payload['config'].empty?
      record_payload.delete('config')
    end
    # prevent updating with empty payload
    if record_payload.empty?
      raise_command_error "Specify at least one option to update.\n#{optparse}"
    end
    payload[rest_object_key] = record_payload
  end
  rest_interface.setopts(options)
  if options[:dry_run]
    print_dry_run rest_interface.dry.update(parent_id, record['id'], payload)
    return
  end
  json_response = rest_interface.update(parent_id, record['id'], payload)
  render_response(json_response, options, rest_object_key) do
    print_green_success "Updated #{rest_label.downcase} #{record['name'] || record['id']}"
    _get(parent_id, record["id"], {}, options)
  end
  return 0, nil
end