morpheus-cli 3.6.3 → 3.6.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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