morpheus-cli 5.3.2 → 5.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/accounts_interface.rb +4 -30
  4. data/lib/morpheus/api/api_client.rb +12 -0
  5. data/lib/morpheus/api/instances_interface.rb +18 -5
  6. data/lib/morpheus/api/load_balancer_pools_interface.rb +9 -0
  7. data/lib/morpheus/api/load_balancer_types_interface.rb +9 -0
  8. data/lib/morpheus/api/load_balancer_virtual_servers_interface.rb +9 -0
  9. data/lib/morpheus/api/load_balancers_interface.rb +4 -53
  10. data/lib/morpheus/api/network_routers_interface.rb +56 -0
  11. data/lib/morpheus/api/secondary_read_interface.rb +25 -0
  12. data/lib/morpheus/api/secondary_rest_interface.rb +42 -0
  13. data/lib/morpheus/api/virtual_images_interface.rb +23 -2
  14. data/lib/morpheus/cli/apps.rb +3 -2
  15. data/lib/morpheus/cli/cli_command.rb +21 -14
  16. data/lib/morpheus/cli/cli_registry.rb +55 -2
  17. data/lib/morpheus/cli/cloud_resource_pools_command.rb +169 -133
  18. data/lib/morpheus/cli/clusters.rb +51 -33
  19. data/lib/morpheus/cli/instances.rb +292 -174
  20. data/lib/morpheus/cli/invoices_command.rb +79 -99
  21. data/lib/morpheus/cli/library_cluster_layouts_command.rb +20 -0
  22. data/lib/morpheus/cli/load_balancer_types.rb +37 -0
  23. data/lib/morpheus/cli/load_balancers.rb +149 -314
  24. data/lib/morpheus/cli/log_settings_command.rb +7 -3
  25. data/lib/morpheus/cli/mixins/load_balancers_helper.rb +156 -0
  26. data/lib/morpheus/cli/mixins/print_helper.rb +11 -0
  27. data/lib/morpheus/cli/mixins/provisioning_helper.rb +123 -101
  28. data/lib/morpheus/cli/mixins/rest_command.rb +657 -0
  29. data/lib/morpheus/cli/monitoring_checks_command.rb +2 -0
  30. data/lib/morpheus/cli/network_routers_command.rb +1183 -185
  31. data/lib/morpheus/cli/networks_command.rb +194 -101
  32. data/lib/morpheus/cli/option_parser.rb +25 -17
  33. data/lib/morpheus/cli/option_types.rb +42 -45
  34. data/lib/morpheus/cli/tenants_command.rb +18 -20
  35. data/lib/morpheus/cli/vdi_pools_command.rb +4 -1
  36. data/lib/morpheus/cli/version.rb +1 -1
  37. data/lib/morpheus/cli/virtual_images.rb +249 -29
  38. data/lib/morpheus/cli.rb +1 -0
  39. data/lib/morpheus/ext/string.rb +41 -0
  40. data/lib/morpheus/formatters.rb +4 -0
  41. data/morpheus-cli.gemspec +1 -1
  42. metadata +13 -4
@@ -32,27 +32,35 @@ module Morpheus
32
32
  #out << "Options:\n"
33
33
  # the default way..
34
34
  # out << summarize().join("")
35
-
36
35
  # super hacky, should be examining the option, not the fully formatted description
37
36
  my_summaries = summarize()
38
- summarize().each do |opt_description|
39
- is_hidden = (@hidden_options || []).find { |hidden_switch|
40
- # opt_description.include?("--#{hidden_switch}")
41
- if hidden_switch.start_with?("-")
42
- opt_description.to_s.strip.start_with?("#{hidden_switch} ")
43
- else
44
- opt_description.to_s.strip.start_with?("--#{hidden_switch} ")
45
- end
46
- }
47
- if is_hidden
48
- if opts[:show_hidden_options]
49
- # out << opt_description + " (hidden)"
50
- out << opt_description
37
+ if opts[:show_hidden_options]
38
+ my_summaries.each do |full_line|
39
+ out << full_line
40
+ end
41
+ else
42
+ on_hidden_option = false
43
+ my_summaries.each do |full_line|
44
+ opt_description = full_line.to_s.strip
45
+ if opt_description.start_with?("-")
46
+ is_hidden = (@hidden_options || []).find { |hidden_switch|
47
+ if hidden_switch.start_with?("-")
48
+ opt_description.start_with?("#{hidden_switch} ")
49
+ else
50
+ opt_description.start_with?("--#{hidden_switch} ")
51
+ end
52
+ }
53
+ if is_hidden
54
+ on_hidden_option = true
55
+ else
56
+ on_hidden_option = false
57
+ out << full_line
58
+ end
51
59
  else
52
- # hidden
60
+ if on_hidden_option == false
61
+ out << full_line
62
+ end
53
63
  end
54
- else
55
- out << opt_description
56
64
  end
57
65
  end
58
66
  end
@@ -61,11 +61,11 @@ module Morpheus
61
61
  ).each do |option_type|
62
62
  context_map = results
63
63
  value = nil
64
- value_found=false
64
+ value_found = false
65
+ field_group = (option_type['fieldGroup'] || 'default').to_s.sub(/options\Z/i, "").strip # avoid "ADVANCED OPTION OPTIONS"
65
66
 
66
- if cur_field_group != (option_type['fieldGroup'] || 'default')
67
- cur_field_group = option_type['fieldGroup']
68
- cur_field_group = cur_field_group.to_s.sub(/options\Z/i, "").strip # avoid "ADVANCED OPTION OPTIONS"
67
+ if cur_field_group != field_group
68
+ cur_field_group = field_group
69
69
  print "\n#{cur_field_group.upcase} OPTIONS\n#{"=" * ("#{cur_field_group} OPTIONS".length)}\n\n"
70
70
  end
71
71
 
@@ -94,46 +94,42 @@ module Morpheus
94
94
  end
95
95
 
96
96
  if !visible_option_check_value.to_s.empty?
97
- # support formats code=value or code:value OR code:(value|value2|value3)
98
- # OR fieldContext.fieldName=value
99
- parts = visible_option_check_value.include?("=") ? visible_option_check_value.split("=") : visible_option_check_value.split(":")
100
- depends_on_code = parts[0]
101
- depends_on_value = parts[1].to_s.strip
102
- depends_on_values = []
103
- if depends_on_value.size > 0
104
- # strip parenthesis
105
- if depends_on_value[0] && depends_on_value[0].chr == "("
106
- depends_on_value = depends_on_value[1..-1]
107
- end
108
- depends_on_value.chomp(")")
109
- depends_on_values = depends_on_value.split("|").collect { |it| it.strip }
110
- end
111
- depends_on_option_type = option_types.find {|it| it["code"] == depends_on_code }
112
- if !depends_on_option_type
113
- depends_on_option_type = option_types.find {|it|
114
- (it['fieldContext'] ? "#{it['fieldContext']}.#{it['fieldName']}" : it['fieldName']) == depends_on_code
115
- }
97
+ match_type = 'any'
98
+
99
+ if visible_option_check_value.include?('::')
100
+ match_type = 'all' if visible_option_check_value.start_with?('matchAll')
101
+ visible_option_check_value = visible_option_check_value[visible_option_check_value.index('::') + 2..-1]
116
102
  end
117
- if depends_on_option_type
118
- # dependent option type has a different value
119
- depends_on_field_key = depends_on_option_type['fieldContext'].nil? || depends_on_option_type['fieldContext'].empty? ? "#{depends_on_option_type['fieldName']}" : "#{depends_on_option_type['fieldContext']}.#{depends_on_option_type['fieldName']}"
120
- found_dep_value = get_object_value(results, depends_on_field_key) || get_object_value(options, depends_on_field_key)
121
-
122
- if depends_on_values.size > 0
123
- # must be in the specified values
124
- # todo: uhh this actually needs to change to parse regex
125
- if !depends_on_values.include?(found_dep_value)
126
- next
127
- end
103
+
104
+ found_dep_value = match_type == 'all' ? true : false
105
+ visible_option_check_value.sub(',', ' ').split(' ').each do |value|
106
+ parts = value.split(':')
107
+ depends_on_code = parts[0]
108
+ depends_on_value = parts.count > 1 ? parts[1].to_s.strip : nil
109
+ depends_on_option_type = option_types.find {|it| it["code"] == depends_on_code }
110
+ if !depends_on_option_type
111
+ depends_on_option_type = option_types.find {|it|
112
+ (it['fieldContext'] ? "#{it['fieldContext']}.#{it['fieldName']}" : it['fieldName']) == depends_on_code
113
+ }
114
+ end
115
+
116
+ if depends_on_option_type
117
+ depends_on_field_key = depends_on_option_type['fieldContext'].nil? || depends_on_option_type['fieldContext'].empty? ? "#{depends_on_option_type['fieldName']}" : "#{depends_on_option_type['fieldContext']}.#{depends_on_option_type['fieldName']}"
128
118
  else
129
- # no value found
130
- if found_dep_value.to_s.empty?
131
- next
132
- end
119
+ depends_on_field_key = depends_on_code
120
+ end
121
+
122
+ field_value = get_object_value(results, depends_on_field_key) ||
123
+ get_object_value(options, depends_on_field_key) ||
124
+ get_object_value(api_params, depends_on_field_key)
125
+
126
+ if !field_value.nil? && (depends_on_value.nil? || depends_on_value.empty? || field_value.match?(depends_on_value))
127
+ found_dep_value = true if match_type != 'all'
128
+ else
129
+ found_dep_value = false if match_type == 'all'
133
130
  end
134
- else
135
- # could not find the dependent option type, proceed and prompt
136
131
  end
132
+ next if !found_dep_value
137
133
  end
138
134
 
139
135
  cur_namespace = options
@@ -246,7 +242,7 @@ module Morpheus
246
242
  # I suppose the entered value should take precedence
247
243
  # api_params = api_params.merge(options) # this might be good enough
248
244
  # dup it
249
- value = select_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), options[:no_prompt], nil, paging_enabled)
245
+ value = select_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).deep_merge(results)), options[:no_prompt], nil, paging_enabled)
250
246
  if value && option_type['type'] == 'multiSelect'
251
247
  value = [value]
252
248
  while self.confirm("Add another #{option_type['fieldLabel']}?", {:default => false}) do
@@ -283,10 +279,10 @@ module Morpheus
283
279
 
284
280
  if option_type['type'] == 'multiSelect'
285
281
  value = [value] if !value.nil? && !value.is_a?(Array)
286
- parent_context_map[parent_ns] = value
287
- else
288
- context_map[field_name] = value
282
+ # parent_context_map[parent_ns] = value
289
283
  end
284
+ context_map[field_name] = value if !(value.nil? || (value.is_a?(Hash) && value.empty?))
285
+ parent_context_map.reject! {|k,v| k == parent_ns && (v.nil? || (v.is_a?(Hash) && v.empty?))}
290
286
  end
291
287
  results
292
288
  end
@@ -957,8 +953,9 @@ module Morpheus
957
953
  out << "\n"
958
954
  out << "#{header}\n"
959
955
  out << "#{'=' * header.length}\n"
956
+
960
957
  select_options.each do |option|
961
- out << " * #{option['name']} [#{option['value']}]\n"
958
+ out << (option['isGroup'] ? "- #{option['name']}\n" : " * #{option['name']} [#{option['value']}]\n")
962
959
  end
963
960
  return out
964
961
  end
@@ -248,41 +248,39 @@ EOT
248
248
 
249
249
 
250
250
  def remove(args)
251
+ params = {}
251
252
  options = {}
252
253
  optparse = Morpheus::Cli::OptionParser.new do |opts|
253
- opts.banner = subcommand_usage("[name]")
254
- build_common_options(opts, options, [:auto_confirm, :json, :remote, :dry_run])
254
+ opts.banner = subcommand_usage("[tenant]")
255
+ opts.on('--remove-resources [on|off]', ['on','off'], "Remove Infrastructure. Default is off.") do |val|
256
+ params[:removeResources] = val.nil? ? 'on' : val
257
+ end
258
+ build_standard_remove_options(opts, options)
259
+ opts.footer = <<-EOT
260
+ Delete a tenant.
261
+ [tenant] is required. This is the name or id of a tenant.
262
+ EOT
255
263
  end
256
264
  optparse.parse!(args)
257
- if args.count < 1
258
- puts optparse
259
- exit 1
260
- end
265
+ verify_args!(args:args, optparse:optparse, count:1)
266
+ optparse.parse!(args)
261
267
  connect(options)
262
268
  begin
263
269
  # allow finding by ID since name is not unique!
264
270
  account = find_account_by_name_or_id(args[0])
265
- exit 1 if account.nil?
266
- unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the account #{account['name']}?")
267
- exit
268
- end
269
- if options[:dry_run] && options[:json]
270
- puts as_json(payload, options)
271
- return 0
271
+ return 1, "tenant not found" if account.nil?
272
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the tenant #{account['name']}?")
273
+ return 9, "aborted command"
272
274
  end
273
275
  @accounts_interface.setopts(options)
274
276
  if options[:dry_run]
275
- print_dry_run @accounts_interface.dry.destroy(account['id'])
277
+ print_dry_run @accounts_interface.dry.destroy(account['id'], params)
276
278
  return
277
279
  end
278
- json_response = @accounts_interface.destroy(account['id'])
279
- if options[:json]
280
- print JSON.pretty_generate(json_response)
281
- print "\n"
282
- else
280
+ json_response = @accounts_interface.destroy(account['id'], params)
281
+ render_response(json_response, options) do
283
282
  print_green_success "Tenant #{account['name']} removed"
284
283
  end
285
-
286
284
  rescue RestClient::Exception => e
287
285
  print_rest_exception(e, options)
288
286
  exit 1
@@ -418,6 +418,7 @@ EOT
418
418
  "Name" => 'name',
419
419
  "Description" => 'description',
420
420
  "Persistent" => lambda {|it| format_boolean(it['persistentUser']) },
421
+ "Recyclable" => lambda {|it| it['recyclable'].nil? ? nil : format_boolean(it['recyclable']) },
421
422
  "Enabled" => lambda {|it| format_boolean(it['enabled']) },
422
423
  "Pool Usage" => lambda {|it|
423
424
  # todo: [== ] 2/8 would be neat generate_usage_bar(...)
@@ -447,12 +448,13 @@ EOT
447
448
  "Max Size" => lambda {|it| format_number(it['maxPoolSize']) rescue '' },
448
449
  "Lease Timeout" => lambda {|it| format_number(it['allocationTimeoutMinutes']) rescue '' },
449
450
  "Persistent" => lambda {|it| format_boolean(it['persistentUser']) },
451
+ "Recyclable" => lambda {|it| it['recyclable'].nil? ? nil : format_boolean(it['recyclable']) },
452
+ "Enabled" => lambda {|it| format_boolean(it['enabled']) },
450
453
  "Allow Copy" => lambda {|it| format_boolean(it['allowCopy']) },
451
454
  "Allow Printer" => lambda {|it| format_boolean(it['allowPrinter']) },
452
455
  "Allow File Share" => lambda {|it| format_boolean(it['allowFileshare']) },
453
456
  "Allow Hypervisor Console" => lambda {|it| format_boolean(it['allowHypervisorConsole']) },
454
457
  "Auto Create User" => lambda {|it| format_boolean(it['autoCreateLocalUserOnReservation']) },
455
- "Enabled" => lambda {|it| format_boolean(it['enabled']) },
456
458
  "Logo" => lambda {|it| it['logo'] || it['imagePath'] },
457
459
  #"Config" => lambda {|it| it['config'] },
458
460
  "Group" => lambda {|it| it['group'] ? it['group']['name'] : nil },
@@ -484,6 +486,7 @@ EOT
484
486
  {'fieldName' => 'maxPoolSize', 'fieldLabel' => 'Max Size', 'type' => 'number', 'required' => true, 'description' => 'Max limit on number of allocations and instances within the pool.'},
485
487
  {'fieldName' => 'allocationTimeoutMinutes', 'fieldLabel' => 'Lease Timeout', 'type' => 'number', 'description' => 'Time (in minutes) after a user disconnects before an allocation is recycled or shutdown depending on persistence.'},
486
488
  {'fieldName' => 'persistentUser', 'fieldLabel' => 'Persistent', 'type' => 'checkbox', 'defaultValue' => false},
489
+ {'fieldName' => 'recyclable', 'fieldLabel' => 'Recyclable', 'type' => 'checkbox', 'defaultValue' => false, 'description' => 'Recyclable VDI Pools only work with cloud types that support snapshot management (i.e. Vmware, Nutanix, VCD)'},
487
490
  {'fieldName' => 'allowCopy', 'fieldLabel' => 'Allow Copy', 'type' => 'checkbox', 'defaultValue' => false},
488
491
  {'fieldName' => 'allowPrinter', 'fieldLabel' => 'Allow Printer', 'type' => 'checkbox', 'defaultValue' => false},
489
492
  {'fieldName' => 'allowFileshare', 'fieldLabel' => 'Allow File Share', 'type' => 'checkbox', 'defaultValue' => false},
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Morpheus
3
3
  module Cli
4
- VERSION = "5.3.2"
4
+ VERSION = "5.3.3"
5
5
  end
6
6
  end
@@ -11,8 +11,7 @@ class Morpheus::Cli::VirtualImages
11
11
  include Morpheus::Cli::ProvisioningHelper
12
12
 
13
13
  register_subcommands :list, :get, :add, :add_file, :remove_file, :update, :remove, :types => :virtual_image_types
14
- alias_subcommand :details, :get
15
- set_default_subcommand :list
14
+ register_subcommands :list_locations, :get_location, :remove_location
16
15
 
17
16
  # def initialize()
18
17
  # # @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
@@ -188,6 +187,7 @@ EOT
188
187
  image = json_response['virtualImage']
189
188
  image_config = image['config'] || {}
190
189
  image_volumes = image['volumes'] || []
190
+ image_locations = image['locations'] || []
191
191
  image_files = json_response['cloudFiles'] || json_response['files']
192
192
  image_type = virtual_image_type_for_name_or_code(image['imageType'])
193
193
  image_type_display = image_type ? "#{image_type['name']}" : image['imageType']
@@ -255,7 +255,7 @@ EOT
255
255
  # print "\n", reset
256
256
  end
257
257
 
258
- if image_files
258
+ if image_files && !image_files.empty?
259
259
  print_h2 "Files (#{image_files.size})"
260
260
  # image_files.each {|image_file|
261
261
  # pretty_filesize = Filesize.from("#{image_file['size']} B").pretty
@@ -270,6 +270,11 @@ EOT
270
270
  print as_pretty_table(image_file_rows, [:filename, :size])
271
271
  # print reset,"\n"
272
272
  end
273
+
274
+ if image_locations && !image_locations.empty?
275
+ print_h2 "Locations", options
276
+ print as_pretty_table(image_locations, virtual_image_location_list_column_definitions.upcase_keys!, options)
277
+ end
273
278
 
274
279
  if options[:details] && image_config && !image_config.empty?
275
280
  print_h2 "Config", options
@@ -689,7 +694,7 @@ EOT
689
694
  image = find_virtual_image_by_name_or_id(image_name)
690
695
  return 1 if image.nil?
691
696
  unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the virtual image filename #{filename}?")
692
- exit
697
+ return 9, "aborted"
693
698
  end
694
699
  @virtual_images_interface.setopts(options)
695
700
  if options[:dry_run]
@@ -709,44 +714,188 @@ EOT
709
714
  end
710
715
 
711
716
  def remove(args)
717
+ params = {}
712
718
  options = {}
713
719
  optparse = Morpheus::Cli::OptionParser.new do |opts|
714
- opts.banner = subcommand_usage("[name]")
715
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
720
+ opts.banner = subcommand_usage("[image] [location]")
721
+ opts.on('--remove-from-cloud [true|false]', String, "Remove from all clouds. Default is true.") do |val|
722
+ options[:options]['removeFromCloud'] = ['','true','on'].include?(val.to_s)
723
+ end
724
+ build_standard_remove_options(opts, options)
725
+ opts.footer = <<-EOT
726
+ Delete a virtual image.
727
+ [image] is required. This is the name or id of a virtual image.
728
+ EOT
716
729
  end
717
730
  optparse.parse!(args)
718
- if args.count < 1
719
- puts optparse
720
- exit 1
731
+ verify_args!(args:args, optparse:optparse, count:1)
732
+ connect(options)
733
+ image = find_virtual_image_by_name_or_id(args[0])
734
+ return 1, "virtual image not found for '#{args[0]}'" if image.nil?
735
+ params.merge!(parse_query_options(options))
736
+ # Delete prompt
737
+ # [ X ] Remove from all clouds
738
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'removeFromCloud', 'fieldLabel' => 'Remove from all clouds', 'type' => 'checkbox', 'defaultValue' => true, 'required' => true, 'description' => "Remove from all clouds"}], options[:options], @api_client)
739
+ remove_from_cloud = v_prompt['removeFromCloud'].to_s == 'true' || v_prompt['removeFromCloud'].to_s == 'on'
740
+ params['removeFromCloud'] = remove_from_cloud
741
+
742
+ # Delete confirmation
743
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the virtual image #{image['name']}?")
744
+ return 9, "aborted"
745
+ end
746
+
747
+ @virtual_images_interface.setopts(options)
748
+ if options[:dry_run]
749
+ print_dry_run @virtual_images_interface.dry.destroy(image['id'], params)
750
+ return
751
+ end
752
+ json_response = @virtual_images_interface.destroy(image['id'], params)
753
+ render_response(json_response, options) do
754
+ print_green_success "Removed virtual image #{image['name']}"
755
+ end
756
+ return 0, nil
757
+ end
758
+
759
+ def list_locations(args)
760
+ params = {}
761
+ options = {}
762
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
763
+ opts.banner = subcommand_usage("[image]")
764
+ build_standard_list_options(opts, options)
765
+ opts.footer = <<-EOT
766
+ List virtual image locations for a specific virtual image.
767
+ [image] is required. This is the name or id of a virtual image.
768
+ EOT
769
+ end
770
+ optparse.parse!(args)
771
+ verify_args!(args:args, optparse:optparse, min:1)
772
+ if args.count > 1
773
+ options[:phrase] = args[1..-1].join(" ")
721
774
  end
722
- image_name = args[0]
723
775
  connect(options)
724
- begin
725
- image = find_virtual_image_by_name_or_id(image_name)
726
- return 1 if image.nil?
727
- unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the virtual image #{image['name']}?")
728
- exit
729
- end
730
- @virtual_images_interface.setopts(options)
731
- if options[:dry_run]
732
- print_dry_run @virtual_images_interface.dry.destroy(image['id'])
733
- return
734
- end
735
- json_response = @virtual_images_interface.destroy(image['id'])
736
- if options[:json]
737
- print JSON.pretty_generate(json_response)
776
+ image = find_virtual_image_by_name_or_id(args[0])
777
+ return 1, "virtual image not found for '#{args[0]}'" if image.nil?
778
+ params.merge!(parse_list_options(options))
779
+ @virtual_images_interface.setopts(options)
780
+ if options[:dry_run]
781
+ print_dry_run @virtual_images_interface.dry.list_locations(image['id'], params)
782
+ return
783
+ end
784
+ json_response = @virtual_images_interface.list_locations(image['id'], params)
785
+ records = json_response['locations']
786
+ render_response(json_response, options, 'virtualImages') do
787
+ title = "Virtual Image Locations"
788
+ subtitles = parse_list_subtitles(options)
789
+ print_h1 title, subtitles
790
+ if records.empty?
791
+ print cyan,"No virtual image locations found.",reset,"\n"
738
792
  else
739
- print "\n", cyan, "Virtual Image #{image['name']} removed", reset, "\n\n"
793
+ print as_pretty_table(records, virtual_image_location_list_column_definitions.upcase_keys!, options)
794
+ print_results_pagination(json_response)
740
795
  end
741
- rescue RestClient::Exception => e
742
- print_rest_exception(e, options)
743
- exit 1
796
+ print reset,"\n"
797
+ end
798
+ return 0, nil
799
+ end
800
+
801
+ def get_location(args)
802
+ params = {}
803
+ options = {}
804
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
805
+ opts.banner = subcommand_usage("[image] [location]")
806
+ build_standard_remove_options(opts, options)
807
+ opts.footer = <<-EOT
808
+ Get details about a virtual image location.
809
+ [image] is required. This is the name or id of a virtual image.
810
+ [location] is required. This is the name or id of a virtual image location.
811
+ EOT
744
812
  end
813
+ optparse.parse!(args)
814
+ verify_args!(args:args, optparse:optparse, count:2)
815
+ connect(options)
816
+ image = find_virtual_image_by_name_or_id(args[0])
817
+ return 1, "virtual image not found for '#{args[0]}'" if image.nil?
818
+ location = find_virtual_image_location_by_name_or_id(image['id'], args[1])
819
+ return 1, "location not found for '#{args[1]}'" if location.nil?
820
+ params.merge!(parse_query_options(options))
821
+ @virtual_images_interface.setopts(options)
822
+ if options[:dry_run]
823
+ print_dry_run @virtual_images_interface.dry.get_location(image['id'], location['id'])
824
+ return 0, nil
825
+ end
826
+ # json_response = @virtual_images_interface.get(image['id'], location['id'])
827
+ json_response = {'location' => location} # skip redundant request
828
+ render_response(json_response, options, 'location') do
829
+ location = json_response['location']
830
+ volumes = location['volumes'] || []
831
+ print_h1 "Virtual Image Location Details", [], options
832
+ print_description_list(virtual_image_location_column_definitions, location, options)
833
+ if volumes && !volumes.empty?
834
+ print_h2 "Volumes", options
835
+ volume_rows = location_volumes.collect do |volume|
836
+ {name: volume['name'], size: Filesize.from("#{volume['rawSize']} B").pretty}
837
+ end
838
+ print cyan
839
+ print as_pretty_table(volume_rows, [:name, :size], options)
840
+ print cyan
841
+ # print "\n", reset
842
+ end
843
+ print reset,"\n"
844
+ end
845
+ return 0, nil
745
846
  end
746
847
 
848
+ def remove_location(args)
849
+ params = {}
850
+ options = {}
851
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
852
+ opts.banner = subcommand_usage("[image] [location]")
853
+ opts.on('--remove-from-cloud [true|false]', String, "Remove from cloud. Default is true.") do |val|
854
+ options[:options]['removeFromCloud'] = ['','true','on'].include?(val.to_s)
855
+ end
856
+ build_standard_remove_options(opts, options)
857
+ opts.footer = <<-EOT
858
+ Delete a virtual image location.
859
+ [image] is required. This is the name or id of a virtual image.
860
+ [location] is required. This is the name or id of a virtual image location.
861
+ EOT
862
+ end
863
+ optparse.parse!(args)
864
+ verify_args!(args:args, optparse:optparse, count:2)
865
+ connect(options)
866
+ image = find_virtual_image_by_name_or_id(args[0])
867
+ return 1, "virtual image not found for '#{args[0]}'" if image.nil?
868
+ location = find_virtual_image_location_by_name_or_id(image['id'], args[1])
869
+ return 1, "location not found for '#{args[1]}'" if location.nil?
870
+
871
+ params.merge!(parse_query_options(options))
872
+
873
+ # Delete prompt
874
+ # [ X ] Remove from cloud
875
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'removeFromCloud', 'fieldLabel' => 'Remove from cloud', 'type' => 'checkbox', 'defaultValue' => true, 'required' => true, 'description' => "Remove from cloud"}], options[:options], @api_client)
876
+ remove_from_cloud = v_prompt['removeFromCloud'].to_s == 'true' || v_prompt['removeFromCloud'].to_s == 'on'
877
+ params['removeFromCloud'] = remove_from_cloud
878
+
879
+ # Delete confirmation
880
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the virtual image location #{location['id']}?")
881
+ return 9, "aborted"
882
+ end
883
+
884
+ @virtual_images_interface.setopts(options)
885
+ if options[:dry_run]
886
+ print_dry_run @virtual_images_interface.dry.destroy_location(image['id'], location['id'], params)
887
+ return
888
+ end
889
+ json_response = @virtual_images_interface.destroy_location(image['id'], location['id'], params)
890
+ render_response(json_response, options) do
891
+ print_green_success "Removed virtual image location #{location['id']}"
892
+ end
893
+ return 0, nil
894
+ end
747
895
 
748
896
  private
749
- def find_virtual_image_by_name_or_id(val)
897
+
898
+ def find_virtual_image_by_name_or_id(val)
750
899
  if val.to_s =~ /\A\d{1,}\Z/
751
900
  return find_virtual_image_by_id(val)
752
901
  else
@@ -911,4 +1060,75 @@ EOT
911
1060
  out
912
1061
  end
913
1062
 
1063
+
1064
+ ## Virtual Image Locations
1065
+
1066
+ def virtual_image_location_object_key
1067
+ "location"
1068
+ end
1069
+
1070
+ def virtual_image_location_list_key
1071
+ "locations"
1072
+ end
1073
+
1074
+ def find_virtual_image_location_by_name_or_id(virtual_image_id, val)
1075
+ if val.to_s =~ /\A\d{1,}\Z/
1076
+ return find_virtual_image_location_by_id(virtual_image_id, val)
1077
+ else
1078
+ return find_virtual_image_location_by_name(virtual_image_id, val)
1079
+ end
1080
+ end
1081
+
1082
+ def virtual_image_location_list_column_definitions
1083
+ virtual_image_location_column_definitions
1084
+ end
1085
+
1086
+ def virtual_image_location_column_definitions
1087
+ {
1088
+ "ID" => 'id',
1089
+ "Name" => 'imageName',
1090
+ "Cloud" => lambda {|it| it['cloud']['name'] rescue '' },
1091
+ "Public" => lambda {|it| format_boolean(it['isPublic']) },
1092
+ "Region" => lambda {|it| it['imageRegion'] },
1093
+ "External ID" => lambda {|it| it['externalId'] },
1094
+ "Price Plan" => lambda {|it| it['pricePlan'] ? it['pricePlan']['name'] : nil },
1095
+ # "Virtual Image" => lambda {|it| it['virtualImage']['name'] rescue '' },
1096
+ # "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
1097
+ # "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) }
1098
+ }
1099
+ end
1100
+
1101
+
1102
+ def find_virtual_image_location_by_id(virtual_image_id, id)
1103
+ begin
1104
+ json_response = @virtual_images_interface.get_location(virtual_image_id, id.to_i)
1105
+ return json_response[virtual_image_location_object_key]
1106
+ rescue RestClient::Exception => e
1107
+ if e.response && e.response.code == 404
1108
+ print_red_alert "Virtual Image Location not found by id '#{id}'"
1109
+ else
1110
+ raise e
1111
+ end
1112
+ end
1113
+ end
1114
+
1115
+ def find_virtual_image_location_by_name(virtual_image_id, name)
1116
+ json_response = @virtual_images_interface.list_locations(virtual_image_id, {imageName: name.to_s})
1117
+ virtual_image_locations = json_response[virtual_image_location_list_key]
1118
+ if virtual_image_locations.empty?
1119
+ print_red_alert "Virtual Image Location not found by name '#{name}'"
1120
+ return nil
1121
+ elsif virtual_image_locations.size > 1
1122
+ print_red_alert "#{virtual_image_locations.size} Virtual Image Locations found by name '#{name}'"
1123
+ print_error "\n"
1124
+ puts_error as_pretty_table(virtual_image_locations, {"ID" => 'id', "NAME" => 'imageName'}, {color:red})
1125
+ print_red_alert "Try using ID instead"
1126
+ print_error reset,"\n"
1127
+ return nil
1128
+ else
1129
+ return virtual_image_locations[0]
1130
+ end
1131
+ end
1132
+
1133
+
914
1134
  end