morpheus-cli 3.6.3 → 3.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -209,27 +209,53 @@ module Morpheus
209
209
  nested_options[name_element] = nested_options[name_element] || {}
210
210
  nested_options = nested_options[name_element]
211
211
  else
212
- nested_options[name_element] = custom_option_args[1]
212
+ val = custom_option_args[1]
213
+ if val.to_s[0] == '{' && val.to_s[-1] == '}'
214
+ begin
215
+ val = JSON.parse(val)
216
+ rescue
217
+ Morpheus::Logging::DarkPrinter.puts "Failed to parse option value '#{val}' as JSON" if Morpheus::Logging.debug?
218
+ end
219
+ end
220
+ nested_options[name_element] = val
213
221
  end
214
222
  end
215
223
  else
216
- custom_options[custom_option_args[0]] = custom_option_args[1]
224
+ val = custom_option_args[1]
225
+ if val.to_s[0] == '{' && val.to_s[-1] == '}'
226
+ begin
227
+ val = JSON.parse(val)
228
+ rescue
229
+ Morpheus::Logging::DarkPrinter.puts "Failed to parse option value '#{val}' as JSON" if Morpheus::Logging.debug?
230
+ end
231
+ end
232
+ custom_options[custom_option_args[0]] = val
217
233
  end
218
234
  # convert "true","on" and "false","off" to true and false
219
- custom_options.booleanize!
235
+ unless options[:skip_booleanize]
236
+ custom_options.booleanize!
237
+ end
220
238
  options[:options] = custom_options
221
239
  end
240
+ opts.on('-P','--prompt', "Always prompts. Use passed options as the default value.") do |val|
241
+ options[:always_prompt] = true
242
+ options[:options] ||= {}
243
+ options[:options][:always_prompt] = true
244
+ end
222
245
  opts.on('-N','--no-prompt', "Skip prompts. Use default values for all optional fields.") do |val|
223
246
  options[:no_prompt] = true
224
- # ew, stored in here for now because options[:options] is what is passed into OptionTypes.prompt() everywhere!
225
247
  options[:options] ||= {}
226
248
  options[:options][:no_prompt] = true
227
249
  end
228
250
 
229
- when :noprompt
251
+ when :prompt
252
+ opts.on('-P','--prompt', "Always prompts. Use passed options as the default value.") do |val|
253
+ options[:always_prompt] = true
254
+ options[:options] ||= {}
255
+ options[:options][:always_prompt] = true
256
+ end
230
257
  opts.on('-N','--no-prompt', "Skip prompts. Use default values for all optional fields.") do |val|
231
258
  options[:no_prompt] = true
232
- # ew, stored in here for now because options[:options] is what is passed into OptionTypes.prompt() everywhere!
233
259
  options[:options] ||= {}
234
260
  options[:options][:no_prompt] = true
235
261
  end
@@ -252,6 +278,37 @@ module Morpheus
252
278
  raise ::OptionParser::InvalidOption.new("Failed to parse payload file: #{payload_file} Error: #{ex.message}")
253
279
  end
254
280
  end
281
+ opts.on('--payload-dir DIRECTORY', String, "Payload from a local directory containing 1-N JSON or YAML files, skip all prompting") do |val|
282
+ options[:payload_dir] = val.to_s
283
+ payload_dir = File.expand_path(options[:payload_dir])
284
+ if !Dir.exists?(payload_dir) || !File.directory?(payload_dir)
285
+ raise ::OptionParser::InvalidOption.new("Directory not found: #{payload_dir}")
286
+ end
287
+ payload = {}
288
+ begin
289
+ merged_payload = {}
290
+ payload_files = []
291
+ payload_files += Dir["#{payload_dir}/*.json"]
292
+ payload_files += Dir["#{payload_dir}/*.yml"]
293
+ payload_files += Dir["#{payload_dir}/*.yaml"]
294
+ if payload_files.empty?
295
+ raise ::OptionParser::InvalidOption.new("No .json/yaml files found in config directory: #{payload_dir}")
296
+ end
297
+ payload_files.each do |payload_file|
298
+ Morpheus::Logging::DarkPrinter.puts "parsing payload file: #{payload_file}" if Morpheus::Logging.debug?
299
+ config_payload = {}
300
+ if payload_file =~ /\.ya?ml\Z/
301
+ config_payload = YAML.load_file(payload_file)
302
+ else
303
+ config_payload = JSON.parse(File.read(payload_file))
304
+ end
305
+ merged_payload.deep_merge!(config_payload)
306
+ end
307
+ options[:payload] = merged_payload
308
+ rescue => ex
309
+ raise ::OptionParser::InvalidOption.new("Failed to parse payload file: #{payload_file} Error: #{ex.message}")
310
+ end
311
+ end
255
312
  opts.on('--payload-json JSON', String, "Payload JSON, skip all prompting") do |val|
256
313
  begin
257
314
  options[:payload] = JSON.parse(val.to_s)
@@ -455,6 +512,12 @@ module Morpheus
455
512
  options[:include_fields] = val
456
513
  end
457
514
 
515
+ when :outfile
516
+ opts.on('--out FILE', String, "Write standard output to a file instead of the terminal.") do |val|
517
+ # could validate directory is writable..
518
+ options[:outfile] = val
519
+ end
520
+
458
521
  when :dry_run
459
522
  opts.on('-d','--dry-run', "Dry Run, print the API request instead of executing it") do
460
523
  options[:dry_run] = true
@@ -754,6 +817,43 @@ module Morpheus
754
817
  return subtitles
755
818
  end
756
819
 
820
+ def print_to_file(txt, filename)
821
+ Morpheus::Logging::DarkPrinter.puts "Writing #{txt.to_s.bytesize} bytes to file #{filename}" if Morpheus::Logging.debug?
822
+ outfile = nil
823
+ begin
824
+ outfile = File.open(File.expand_path(filename), 'w')
825
+ outfile.print(txt)
826
+ return 0
827
+ rescue => ex
828
+ puts_error "Error printing to outfile '#{filename}'. Error: #{ex}"
829
+ return 1
830
+ ensure
831
+ outfile.close if outfile
832
+ end
833
+ end
834
+
835
+ # basic rendering for options :json, :yaml, :csv, :fields, and :outfile
836
+ # returns the string rendered, or nil if nothing was rendered.
837
+ def render_with_format(json_response, options, object_key=nil)
838
+ output = nil
839
+ if options[:json]
840
+ output = as_json(json_response, options, object_key)
841
+ elsif options[:yaml]
842
+ output = as_yaml(json_response, options, object_key)
843
+ elsif options[:csv]
844
+ row = object_key ? json_response[object_key] : json_response
845
+ output = records_as_csv([row], options)
846
+ end
847
+ if output
848
+ if options[:outfile]
849
+ print_to_file(output, options[:outfile])
850
+ else
851
+ puts output
852
+ end
853
+ end
854
+ return output
855
+ end
856
+
757
857
  module ClassMethods
758
858
 
759
859
  def set_command_name(cmd_name)
@@ -379,7 +379,7 @@ class Morpheus::Cli::CloudDatastoresCommand
379
379
  elsif datastores.size > 1
380
380
  print_red_alert "#{datastores.size} datastores found by name #{name}"
381
381
  # print_datastores_table(datastores, {color: red})
382
- rows = datastores.collect do |datastore|
382
+ rows = datastores.collect do |it|
383
383
  {id: it['id'], name: it['name']}
384
384
  end
385
385
  print red
@@ -0,0 +1,45 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ # This is for printing the content of files(s)
4
+ class Morpheus::Cli::CatCommand
5
+ include Morpheus::Cli::CliCommand
6
+ set_command_name :cat
7
+ set_command_hidden
8
+
9
+ def handle(args)
10
+ append_newline = true
11
+ options = {}
12
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
13
+ opts.banner = "Usage: morpheus #{command_name} [file ...]"
14
+ build_common_options(opts, options, [])
15
+ opts.footer = "Concatenate and print files." + "\n" +
16
+ "[file] is required. This is the name of a file. Supports many [file] arguments."
17
+ end
18
+ optparse.parse!(args)
19
+ if args.count < 1
20
+ print_error Morpheus::Terminal.angry_prompt
21
+ puts_error "#{command_name} expects 1-N arguments and received #{args.count} #{args}\n#{optparse}"
22
+ return 1
23
+ end
24
+
25
+ arg_files = args
26
+ arg_files.each do |arg_file|
27
+ arg_file = File.expand_path(arg_file)
28
+ if !File.exists?(arg_file)
29
+ print_error Morpheus::Terminal.angry_prompt
30
+ puts_error "#{command_name}: file not found: '#{arg_file}'"
31
+ #print_red_alert "morpheus cat: file not found: '#{arg_file}'"
32
+ return 1
33
+ end
34
+ if File.directory?(arg_file)
35
+ print_red_alert "morpheus cat: file is a directory: '#{arg_file}'"
36
+ return 1
37
+ end
38
+ file_contents = File.read(arg_file)
39
+ print file_contents.to_s
40
+ return 0
41
+ end
42
+ return true
43
+ end
44
+
45
+ end
@@ -33,15 +33,14 @@ class Morpheus::Cli::ContainersCommand
33
33
  opts.on( nil, '--actions', "Display Available Actions" ) do
34
34
  options[:include_available_actions] = true
35
35
  end
36
- opts.on('--refresh [status]', String, "Refresh until status is reached. Default status is running.") do |val|
37
- if val.to_s.empty?
38
- options[:refresh_until_status] = "running,failed"
39
- else
40
- options[:refresh_until_status] = val.to_s.downcase
36
+ opts.on('--refresh [SECONDS]', String, "Refresh until status is running,failed. Default interval is 5 seconds.") do |val|
37
+ options[:refresh_until_status] ||= "running,failed"
38
+ if !val.to_s.empty?
39
+ options[:refresh_interval] = val.to_f
41
40
  end
42
41
  end
43
- opts.on('--refresh-interval seconds', String, "Refresh interval. Default is 5 seconds.") do |val|
44
- options[:refresh_interval] = val.to_f
42
+ opts.on('--refresh-until STATUS', String, "Refresh until a specified status is reached.") do |val|
43
+ options[:refresh_until_status] = val.to_s.downcase
45
44
  end
46
45
  build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
47
46
  end
@@ -140,8 +139,7 @@ class Morpheus::Cli::ContainersCommand
140
139
  statuses = options[:refresh_until_status].to_s.downcase.split(",").collect {|s| s.strip }.select {|s| !s.to_s.empty? }
141
140
  if !statuses.include?(container['status'])
142
141
  print cyan
143
- print "Status is #{container['status'] || 'unknown'}. Refreshing in #{options[:refresh_interval]} seconds"
144
- #sleep(options[:refresh_interval])
142
+ print cyan, "Refreshing in #{options[:refresh_interval]} seconds"
145
143
  sleep_with_dots(options[:refresh_interval])
146
144
  print "\n"
147
145
  _get(arg, options)
@@ -23,7 +23,9 @@ class Morpheus::Cli::Echo
23
23
  var_map.merge!(DEFAULT_VARIABLE_MAP)
24
24
  appliance = ::Morpheus::Cli::Remote.load_active_remote()
25
25
  if appliance
26
- var_map.merge!({'%remote' => appliance[:name], '%remote_url' => appliance[:host], '%username' => appliance[:username]})
26
+ var_map.merge!({'%remote' => appliance[:name], '%remote_url' => appliance[:host].to_s, '%username' => appliance[:username].to_s})
27
+ else
28
+ var_map.merge!({'%remote' => '', '%remote_url' => '', '%username' => ''})
27
29
  end
28
30
  @output_variable_map = var_map
29
31
  end
@@ -37,11 +37,11 @@ class Morpheus::Cli::ExecutionRequestCommand
37
37
  optparse = Morpheus::Cli::OptionParser.new do |opts|
38
38
  opts.banner = subcommand_usage("[uid]")
39
39
  build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
40
- opts.on('--refresh', String, "Refresh until execution is finished.") do |val|
40
+ opts.on('--refresh [SECONDS]', String, "Refresh until execution is finished. Default interval is 5 seconds.") do |val|
41
41
  options[:refresh_until_finished] = true
42
- end
43
- opts.on('--refresh-interval seconds', String, "Refresh interval. Default is 5 seconds.") do |val|
44
- options[:refresh_interval] = val.to_f
42
+ if !val.to_s.empty?
43
+ options[:refresh_interval] = val.to_f
44
+ end
45
45
  end
46
46
  opts.footer = "Get details about an execution request." + "\n" +
47
47
  "[uid] is required. This is the unique id of an execution request."
@@ -40,6 +40,10 @@ module Morpheus::Cli::ExpressionParser
40
40
 
41
41
  # parse an expression of morpheus commands into a list of expressions
42
42
  def self.parse(input)
43
+ # the input is a comment
44
+ if input.to_s =~ /^\s*#/
45
+ return [input]
46
+ end
43
47
  result = []
44
48
  # first, build up a temporary command string
45
49
  # swap in well known tokens so we can split it safely
@@ -37,11 +37,11 @@ class Morpheus::Cli::FileCopyRequestCommand
37
37
  optparse = Morpheus::Cli::OptionParser.new do |opts|
38
38
  opts.banner = subcommand_usage("[uid]")
39
39
  build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
40
- opts.on('--refresh', String, "Refresh until file copy is finished.") do |val|
40
+ opts.on('--refresh [SECONDS]', String, "Refresh until execution is finished. Default interval is 5 seconds.") do |val|
41
41
  options[:refresh_until_finished] = true
42
- end
43
- opts.on('--refresh-interval seconds', String, "Refresh interval. Default is 5 seconds.") do |val|
44
- options[:refresh_interval] = val.to_f
42
+ if !val.to_s.empty?
43
+ options[:refresh_interval] = val.to_f
44
+ end
45
45
  end
46
46
  opts.footer = "Get details about a file copy request." + "\n" +
47
47
  "[uid] is required. This is the unique id of a file copy request."
@@ -254,15 +254,14 @@ class Morpheus::Cli::Hosts
254
254
  options = {}
255
255
  optparse = Morpheus::Cli::OptionParser.new do |opts|
256
256
  opts.banner = subcommand_usage("[name]")
257
- opts.on('--refresh [status]', String, "Refresh until status is reached. Default status is provisioned.") do |val|
258
- if val.to_s.empty?
259
- options[:refresh_until_status] = "provisioned,failed"
260
- else
261
- options[:refresh_until_status] = val.to_s.downcase
257
+ opts.on('--refresh [SECONDS]', String, "Refresh until status is running,failed. Default interval is 5 seconds.") do |val|
258
+ options[:refresh_until_status] ||= "provisioned,failed"
259
+ if !val.to_s.empty?
260
+ options[:refresh_interval] = val.to_f
262
261
  end
263
262
  end
264
- opts.on('--refresh-interval seconds', String, "Refresh interval. Default is 5 seconds.") do |val|
265
- options[:refresh_interval] = val.to_f
263
+ opts.on('--refresh-until STATUS', String, "Refresh until a specified status is reached.") do |val|
264
+ options[:refresh_until_status] = val.to_s.downcase
266
265
  end
267
266
  build_common_options(opts, options, [:json, :csv, :yaml, :fields, :dry_run, :remote])
268
267
  end
@@ -337,8 +336,7 @@ class Morpheus::Cli::Hosts
337
336
  statuses = options[:refresh_until_status].to_s.downcase.split(",").collect {|s| s.strip }.select {|s| !s.to_s.empty? }
338
337
  if !statuses.include?(server['status'])
339
338
  print cyan
340
- print "Status is #{server['status'] || 'unknown'}. Refreshing in #{options[:refresh_interval]} seconds"
341
- #sleep(options[:refresh_interval])
339
+ print cyan, "Refreshing in #{options[:refresh_interval]} seconds"
342
340
  sleep_with_dots(options[:refresh_interval])
343
341
  print "\n"
344
342
  _get(arg, options)
@@ -763,16 +761,16 @@ class Morpheus::Cli::Hosts
763
761
  # query_params[:removeResources] = 'off'
764
762
  # end
765
763
  opts.on('--remove-resources [on|off]', ['on','off'], "Remove Infrastructure. Default is on if server is managed.") do |val|
766
- query_params[:removeResources] = val
764
+ query_params[:removeResources] = val.nil? ? 'on' : val
767
765
  end
768
766
  opts.on('--preserve-volumes [on|off]', ['on','off'], "Preserve Volumes. Default is off.") do |val|
769
- query_params[:preserveVolumes] = val
767
+ query_params[:preserveVolumes] = val.nil? ? 'on' : val
770
768
  end
771
769
  opts.on('--remove-instances [on|off]', ['on','off'], "Remove Associated Instances. Default is off.") do |val|
772
- query_params[:removeInstances] = val
770
+ query_params[:removeInstances] = val.nil? ? 'on' : val
773
771
  end
774
772
  opts.on('--release-eips [on|off]', ['on','off'], "Release EIPs, default is on. Amazon only.") do |val|
775
- params[:releaseEIPs] = val
773
+ params[:releaseEIPs] = val.nil? ? 'on' : val
776
774
  end
777
775
  opts.on( '-f', '--force', "Force Delete" ) do
778
776
  query_params[:force] = 'on'
@@ -226,6 +226,9 @@ class Morpheus::Cli::Instances
226
226
  opts.on( '--name NAME', "Instance Name" ) do |val|
227
227
  options[:instance_name] = val
228
228
  end
229
+ opts.on("--description [TEXT]", String, "Description") do |val|
230
+ options[:description] = val.to_s
231
+ end
229
232
  opts.on("--copies NUMBER", Integer, "Number of copies to provision") do |val|
230
233
  options[:copies] = val.to_i
231
234
  end
@@ -250,8 +253,8 @@ class Morpheus::Cli::Instances
250
253
  opts.on("--expire-days NUMBER", Integer, "Automation: Expiration Days") do |val|
251
254
  options[:expire_days] = val.to_i
252
255
  end
253
- opts.on("--create-backup on|off", String, "Automation: Create Backups. Default is off.") do |val|
254
- options[:create_backup] = ['on','true','1'].include?(val.to_s.downcase) ? 'on' : 'off'
256
+ opts.on("--create-backup [on|off]", String, "Automation: Create Backups.") do |val|
257
+ options[:create_backup] = ['on','true','1',''].include?(val.to_s.downcase) ? 'on' : 'off'
255
258
  end
256
259
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote, :quiet])
257
260
  opts.footer = "Create a new instance." + "\n" +
@@ -301,17 +304,17 @@ class Morpheus::Cli::Instances
301
304
  end
302
305
  end
303
306
  end
304
-
307
+ payload['instance'] ||= {}
305
308
  if options[:instance_name]
306
- payload['instance'] ||= payload['instance']
307
309
  payload['instance']['name'] = options[:instance_name]
308
310
  end
309
311
  payload[:copies] = options[:copies] if options[:copies] && options[:copies] > 0
310
312
  payload[:layoutSize] = options[:layout_size] if options[:layout_size] && options[:layout_size] > 0 # aka Scale Factor
311
- payload[:createBackup] = options[:create_backup] ? 'on' : 'off' if options[:create_backup] == true
313
+ payload[:createBackup] = options[:create_backup] if !options[:create_backup].nil?
312
314
  payload['instance']['expireDays'] = options[:expire_days] if options[:expire_days]
313
315
  payload['instance']['shutdownDays'] = options[:shutdown_days] if options[:shutdown_days]
314
316
  if options.key?(:create_user)
317
+ payload['config'] ||= {}
315
318
  payload['config']['createUser'] = options[:create_user]
316
319
  end
317
320
  if options[:user_group_id]
@@ -743,15 +746,14 @@ class Morpheus::Cli::Instances
743
746
  opts.on( nil, '--scaling', "Display Instance Scaling Settings" ) do
744
747
  options[:include_scaling] = true
745
748
  end
746
- opts.on('--refresh [status]', String, "Refresh until status is reached. Default status is running.") do |val|
747
- if val.to_s.empty?
748
- options[:refresh_until_status] = "running,failed"
749
- else
750
- options[:refresh_until_status] = val.to_s.downcase
749
+ opts.on('--refresh [SECONDS]', String, "Refresh until status is running,failed. Default interval is 5 seconds.") do |val|
750
+ options[:refresh_until_status] ||= "running,failed"
751
+ if !val.to_s.empty?
752
+ options[:refresh_interval] = val.to_f
751
753
  end
752
754
  end
753
- opts.on('--refresh-interval seconds', String, "Refresh interval. Default is 5 seconds.") do |val|
754
- options[:refresh_interval] = val.to_f
755
+ opts.on('--refresh-until STATUS', String, "Refresh until a specified status is reached.") do |val|
756
+ options[:refresh_until_status] = val.to_s.downcase
755
757
  end
756
758
  # opts.on( nil, '--threshold', "Alias for --scaling" ) do
757
759
  # options[:include_scaling] = true
@@ -957,9 +959,7 @@ class Morpheus::Cli::Instances
957
959
  end
958
960
  statuses = options[:refresh_until_status].to_s.downcase.split(",").collect {|s| s.strip }.select {|s| !s.to_s.empty? }
959
961
  if !statuses.include?(instance['status'])
960
- print cyan
961
- print "Status is #{instance['status'] || 'unknown'}. Refreshing in #{options[:refresh_interval]} seconds"
962
- #sleep(options[:refresh_interval])
962
+ print cyan, "Refreshing in #{options[:refresh_interval]} seconds"
963
963
  sleep_with_dots(options[:refresh_interval])
964
964
  print "\n"
965
965
  _get(arg, options)
@@ -1066,7 +1066,7 @@ class Morpheus::Cli::Instances
1066
1066
  options = {}
1067
1067
  optparse = Morpheus::Cli::OptionParser.new do |opts|
1068
1068
  opts.banner = subcommand_usage("[name]")
1069
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
1069
+ build_common_options(opts, options, [:json, :dry_run, :remote])
1070
1070
  end
1071
1071
  optparse.parse!(args)
1072
1072
  if args.count < 1
@@ -1086,20 +1086,68 @@ class Morpheus::Cli::Instances
1086
1086
  puts as_json(json_response, options)
1087
1087
  return
1088
1088
  end
1089
- backups = json_response['backups']
1089
+
1090
+ if json_response['backups'] && json_response['backups'][0] && json_response['backups'][0]['backupResults']
1091
+ # new format
1092
+ print_h1 "Instance Backups: #{instance['name']} (#{instance['instanceType']['name']})"
1090
1093
 
1091
- print_h1 "Instance Backups: #{instance['name']} (#{instance['instanceType']['name']})"
1092
- backup_rows = backups.collect {|r|
1093
- it = r['backup']
1094
- {id: it['id'], name: it['name'], dateCreated: format_local_dt(it['dateCreated'])}
1095
- }
1096
- print cyan
1097
- puts as_pretty_table backup_rows, [
1098
- :id,
1099
- :name,
1100
- {:dateCreated => {:display_name => "Date Created"} }
1101
- ]
1102
- print reset, "\n"
1094
+
1095
+ backup = json_response['backups'][0]
1096
+
1097
+ description_cols = {
1098
+ "Backup ID" => lambda {|it| it['id'] },
1099
+ "Name" => lambda {|it| it['name'] },
1100
+ "Type" => lambda {|it| it['backupType'] ? (it['backupType']['name'] || it['backupType']['code']) : '' },
1101
+ "Storage" => lambda {|it| it['storageProvider'] ? it['storageProvider']['name'] : '' },
1102
+ "Schedule" => lambda {|it| it['cronDescription'] || it['cronExpression'] }
1103
+ }
1104
+ print_description_list(description_cols, backup)
1105
+
1106
+ backup_results = backup ? backup['backupResults'] : nil
1107
+ backup_rows = backup_results.collect {|it|
1108
+ status_str = it['status'].to_s.upcase
1109
+ # 'START_REQUESTED' //START_REQUESTED, IN_PROGRESS, CANCEL_REQUESTED, CANCELLED, SUCCEEDED, FAILED
1110
+ if status_str == 'SUCCEEDED'
1111
+ status_str = "#{green}#{status_str.upcase}#{cyan}"
1112
+ elsif status_str == 'FAILED'
1113
+ status_str = "#{red}#{status_str.upcase}#{cyan}"
1114
+ else
1115
+ status_str = "#{cyan}#{status_str.upcase}#{cyan}"
1116
+ end
1117
+ {id: it['id'], startDate: format_local_dt(it['dateCreated']), duration: format_duration_milliseconds(it['durationMillis']),
1118
+ size: format_bytes(it['sizeInMb'], 'MB'), status: status_str }
1119
+ }
1120
+ print_h1 "Backup Results"
1121
+ print cyan
1122
+ puts as_pretty_table backup_rows, [
1123
+ :id,
1124
+ {:startDate => {:display_name => "Started"} },
1125
+ :duration,
1126
+ :size,
1127
+ :status
1128
+ ]
1129
+ print reset, "\n"
1130
+ elsif json_response['backups'].size == 0
1131
+ # no backup configured
1132
+ print_h1 "Instance Backups: #{instance['name']} (#{instance['instanceType']['name']})"
1133
+ print "#{yellow}No backups configured#{reset}\n\n"
1134
+ else
1135
+ # old format
1136
+ print_h1 "Instance Backups: #{instance['name']} (#{instance['instanceType']['name']})"
1137
+ backups = json_response['backups']
1138
+ backup_rows = backups.collect {|r|
1139
+ it = r['backup']
1140
+ {id: it['id'], name: it['name'], dateCreated: format_local_dt(it['dateCreated'])}
1141
+ }
1142
+ print cyan
1143
+ puts as_pretty_table backup_rows, [
1144
+ :id,
1145
+ :name,
1146
+ {:dateCreated => {:display_name => "Date Created"} }
1147
+ ]
1148
+ print reset, "\n"
1149
+ end
1150
+ return 0
1103
1151
  rescue RestClient::Exception => e
1104
1152
  print_rest_exception(e, options)
1105
1153
  exit 1
@@ -1114,23 +1162,26 @@ class Morpheus::Cli::Instances
1114
1162
  opts.on( '-g', '--group GROUP', "Group Name or ID for the new instance" ) do |val|
1115
1163
  options[:group] = val
1116
1164
  end
1165
+ opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID for the new instance" ) do |val|
1166
+ options[:cloud] = val
1167
+ end
1117
1168
  build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
1118
1169
  end
1119
1170
  optparse.parse!(args)
1120
1171
  if args.count < 1
1121
1172
  puts optparse
1122
- exit 1
1123
- end
1124
- if !options[:group]
1125
- print_red_alert "GROUP is required."
1126
- puts optparse
1127
- exit 1
1173
+ return 1
1128
1174
  end
1175
+ # if !options[:group]
1176
+ # print_red_alert "GROUP is required."
1177
+ # puts optparse
1178
+ # exit 1
1179
+ # end
1129
1180
  connect(options)
1130
1181
  begin
1131
1182
  options[:options] ||= {}
1132
1183
  # use the -g GROUP or active group by default
1133
- options[:options]['group'] ||= options[:group] # || @active_group_id # always choose a group for now?
1184
+ options[:options]['group'] ||= options[:group] || @active_group_id
1134
1185
  # support [new-name]
1135
1186
  # if args[1]
1136
1187
  # options[:options]['name'] = args[1]
@@ -1143,6 +1194,10 @@ class Morpheus::Cli::Instances
1143
1194
  payload.merge!(params)
1144
1195
  payload['group'] = {id: group['id']}
1145
1196
 
1197
+ cloud = options[:cloud] ? find_zone_by_name_or_id(nil, options[:cloud]) : nil
1198
+ if cloud
1199
+ payload['cloud'] = {id: cloud['id']}
1200
+ end
1146
1201
  instance = find_instance_by_name_or_id(args[0])
1147
1202
  unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to clone the instance '#{instance['name']}'?", options)
1148
1203
  exit 1
@@ -1808,8 +1863,21 @@ class Morpheus::Cli::Instances
1808
1863
  puts as_json(json_response, options)
1809
1864
  return 0
1810
1865
  else
1811
- puts "Backup initiated."
1812
- return 0
1866
+ bad_results = []
1867
+ if json_response['results']
1868
+ json_response['results'].each do |result_id, result|
1869
+ if result['success'] != true
1870
+ bad_results << result['msg'] || "Failed to create backup for instance #{result_id}"
1871
+ end
1872
+ end
1873
+ end
1874
+ if bad_results.empty?
1875
+ print_green_success "Backup initiated."
1876
+ return 0
1877
+ else
1878
+ print_red_alert bad_results.join("\n")
1879
+ return 1
1880
+ end
1813
1881
  end
1814
1882
  rescue RestClient::Exception => e
1815
1883
  print_rest_exception(e, options)
@@ -1826,10 +1894,10 @@ class Morpheus::Cli::Instances
1826
1894
  query_params[:keepBackups] = 'on'
1827
1895
  end
1828
1896
  opts.on('--preserve-volumes [on|off]', ['on','off'], "Preserve Volumes. Default is off. Applies to certain types only.") do |val|
1829
- query_params[:preserveVolumes] = val
1897
+ query_params[:preserveVolumes] = val.nil? ? 'on' : val
1830
1898
  end
1831
1899
  opts.on('--releaseEIPs [on|off]', ['on','off'], "Release EIPs. Default is on. Applies to Amazon only.") do |val|
1832
- query_params[:releaseEIPs] = val
1900
+ query_params[:releaseEIPs] = val.nil? ? 'on' : val
1833
1901
  end
1834
1902
  opts.on( '-f', '--force', "Force Delete" ) do
1835
1903
  query_params[:force] = 'on'
@@ -2128,7 +2196,7 @@ class Morpheus::Cli::Instances
2128
2196
  if options[:json]
2129
2197
  puts as_json(json_response, options)
2130
2198
  else
2131
- puts "Snapshot import initiated."
2199
+ print_green_success "Snapshot import initiated."
2132
2200
  end
2133
2201
  return 0
2134
2202
  rescue RestClient::Exception => e