morpheus-cli 3.6.28 → 3.6.29

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/morpheus/api/api_client.rb +16 -0
  3. data/lib/morpheus/api/cloud_folders_interface.rb +47 -0
  4. data/lib/morpheus/api/cloud_resource_pools_interface.rb +47 -0
  5. data/lib/morpheus/api/network_types_interface.rb +26 -0
  6. data/lib/morpheus/api/reports_interface.rb +77 -0
  7. data/lib/morpheus/api/security_group_rules_interface.rb +6 -0
  8. data/lib/morpheus/api/security_groups_interface.rb +21 -15
  9. data/lib/morpheus/cli.rb +3 -0
  10. data/lib/morpheus/cli/accounts.rb +1 -1
  11. data/lib/morpheus/cli/apps.rb +2 -2
  12. data/lib/morpheus/cli/archives_command.rb +18 -18
  13. data/lib/morpheus/cli/blueprints_command.rb +1 -1
  14. data/lib/morpheus/cli/boot_scripts_command.rb +6 -6
  15. data/lib/morpheus/cli/cli_command.rb +4 -0
  16. data/lib/morpheus/cli/cloud_datastores_command.rb +58 -20
  17. data/lib/morpheus/cli/cloud_folders_command.rb +463 -0
  18. data/lib/morpheus/cli/cloud_resource_pools_command.rb +707 -0
  19. data/lib/morpheus/cli/clouds.rb +2 -0
  20. data/lib/morpheus/cli/hosts.rb +33 -8
  21. data/lib/morpheus/cli/instances.rb +79 -54
  22. data/lib/morpheus/cli/library_option_lists_command.rb +1 -1
  23. data/lib/morpheus/cli/library_option_types_command.rb +1 -1
  24. data/lib/morpheus/cli/mixins/provisioning_helper.rb +11 -2
  25. data/lib/morpheus/cli/monitoring_contacts_command.rb +1 -1
  26. data/lib/morpheus/cli/monitoring_incidents_command.rb +1 -1
  27. data/lib/morpheus/cli/network_services_command.rb +7 -3
  28. data/lib/morpheus/cli/networks_command.rb +164 -63
  29. data/lib/morpheus/cli/option_types.rb +16 -15
  30. data/lib/morpheus/cli/policies_command.rb +76 -9
  31. data/lib/morpheus/cli/preseed_scripts_command.rb +2 -2
  32. data/lib/morpheus/cli/remote.rb +26 -28
  33. data/lib/morpheus/cli/reports_command.rb +594 -0
  34. data/lib/morpheus/cli/security_group_rules.rb +5 -1
  35. data/lib/morpheus/cli/security_groups.rb +882 -45
  36. data/lib/morpheus/cli/tasks.rb +158 -23
  37. data/lib/morpheus/cli/tenants_command.rb +1 -1
  38. data/lib/morpheus/cli/users.rb +1 -1
  39. data/lib/morpheus/cli/version.rb +1 -1
  40. metadata +9 -2
@@ -33,6 +33,7 @@ class Morpheus::Cli::PoliciesCommand
33
33
  @clouds_interface = @api_client.clouds
34
34
  @groups_interface = @api_client.groups
35
35
  @users_interface = @api_client.users
36
+ @roles_interface = @api_client.roles
36
37
  @active_group_id = Morpheus::Cli::Groups.active_groups[@appliance_name]
37
38
  end
38
39
 
@@ -54,6 +55,9 @@ class Morpheus::Cli::PoliciesCommand
54
55
  opts.on( '-u', '--user USER', "Username or ID" ) do |val|
55
56
  options[:user] = val
56
57
  end
58
+ opts.on( '--role ROLE', String, "Role Authority or ID" ) do |val|
59
+ options[:role] = val
60
+ end
57
61
  opts.on( '-G', '--global', "Global policies only" ) do
58
62
  params[:global] = true
59
63
  end
@@ -71,12 +75,19 @@ class Morpheus::Cli::PoliciesCommand
71
75
  elsif options[:user]
72
76
  user = find_user_by_username_or_id(nil, options[:user])
73
77
  return 1 if user.nil?
78
+ elsif options[:role]
79
+ role = find_role_by_name_or_id(nil, options[:role])
80
+ return 1 if role.nil?
74
81
  end
75
82
  params.merge!(parse_list_options(options))
76
83
  if user
77
84
  params['refType'] = 'User'
78
85
  params['refId'] = user['id']
79
86
  end
87
+ if role
88
+ params['refType'] = 'Role'
89
+ params['refId'] = role['id']
90
+ end
80
91
  @policies_interface.setopts(options)
81
92
  @group_policies_interface.setopts(options)
82
93
  @cloud_policies_interface.setopts(options)
@@ -86,11 +97,6 @@ class Morpheus::Cli::PoliciesCommand
86
97
  elsif cloud
87
98
  print_dry_run @cloud_policies_interface.dry.list(cloud['id'], params)
88
99
  else
89
- # global
90
- if user
91
- params['refType'] = 'User'
92
- params['refId'] = user['id']
93
- end
94
100
  print_dry_run @policies_interface.dry.list(params)
95
101
  end
96
102
  return 0
@@ -125,6 +131,9 @@ class Morpheus::Cli::PoliciesCommand
125
131
  if user
126
132
  subtitles << "User: #{user['username']}".strip
127
133
  end
134
+ if role
135
+ subtitles << "Role: #{role['authority']}".strip
136
+ end
128
137
  if params[:global]
129
138
  subtitles << "(Global)".strip
130
139
  end
@@ -152,13 +161,14 @@ class Morpheus::Cli::PoliciesCommand
152
161
  group: policy['site'] ? policy['site']['name'] : '',
153
162
  cloud: policy['zone'] ? policy['zone']['name'] : '',
154
163
  user: policy['user'] ? policy['user']['username'] : '',
164
+ role: policy['role'] ? policy['role']['authority'] : '',
155
165
  tenants: truncate_string(format_tenants(policy['accounts']), 15),
156
166
  config: truncate_string(config_str, 50),
157
167
  enabled: policy['enabled'] ? 'Yes' : 'No',
158
168
  }
159
169
  row
160
170
  }
161
- columns = [:id, :name, :description, :group, :cloud, :user, :tenants, :type, :config, :enabled]
171
+ columns = [:id, :name, :description, :group, :cloud, :user, :role, :tenants, :type, :config, :enabled]
162
172
  if group || cloud || user
163
173
  columns = columns - [:group, :cloud, :user]
164
174
  end
@@ -261,6 +271,22 @@ class Morpheus::Cli::PoliciesCommand
261
271
  "Type" => lambda {|it| it['policyType'] ? it['policyType']['name'] : '' },
262
272
  "Group" => lambda {|it| it['site'] ? it['site']['name'] : '' },
263
273
  "Cloud" => lambda {|it| it['zone'] ? it['zone']['name'] : '' },
274
+ "User" => lambda {|it| it['user'] ? it['user']['username'] : '' },
275
+ "Role" => lambda {|it|
276
+ str = ""
277
+ if it['role']
278
+ str << it['role']['authority']
279
+ end
280
+ str
281
+ },
282
+ "Each User" => lambda {|it|
283
+ #format_boolean it['eachUser']
284
+ if it['eachUser']
285
+ 'Yes, Apply individually to each user in role'
286
+ else
287
+ 'No'
288
+ end
289
+ },
264
290
  # "All Accounts" => lambda {|it| it['allAccounts'] ? 'Yes' : 'No' },
265
291
  # "Ref Type" => 'refType',
266
292
  # "Ref ID" => 'refId',
@@ -268,6 +294,19 @@ class Morpheus::Cli::PoliciesCommand
268
294
  "Tenants" => lambda {|it| format_tenants(policy["accounts"]) },
269
295
  "Enabled" => lambda {|it| it['enabled'] ? 'Yes' : 'No' }
270
296
  }
297
+ if policy['site'].nil?
298
+ description_cols.delete("Group")
299
+ end
300
+ if policy['zone'].nil?
301
+ description_cols.delete("Cloud")
302
+ end
303
+ if policy['user'].nil?
304
+ description_cols.delete("User")
305
+ end
306
+ if policy['role'].nil?
307
+ description_cols.delete("Role")
308
+ description_cols.delete("Each User")
309
+ end
271
310
  print_description_list(description_cols, policy)
272
311
  # print reset,"\n"
273
312
 
@@ -296,6 +335,13 @@ class Morpheus::Cli::PoliciesCommand
296
335
  opts.on( '-u', '--user USER', "Username or ID, for scoping the policy to a user" ) do |val|
297
336
  options[:user] = val
298
337
  end
338
+ opts.on( '--role ROLE', String, "Role Authority or ID, for scoping the policy to a user" ) do |val|
339
+ options[:role] = val
340
+ end
341
+ opts.on('--each-user [on|off]', String, "Apply individually to each user in role, for use with policy scoped by role.") do |val|
342
+ options['eachUser'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s.empty?
343
+ end
344
+
299
345
  opts.on('-t', '--type ID', "Policy Type Name or ID") do |val|
300
346
  options['type'] = val
301
347
  end
@@ -313,7 +359,7 @@ class Morpheus::Cli::PoliciesCommand
313
359
  end
314
360
  end
315
361
  opts.on('--enabled [on|off]', String, "Can be used to disable a policy") do |val|
316
- options['enabled'] = val.to_s == 'on' || val.to_s == 'true'
362
+ options['enabled'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s.empty?
317
363
  end
318
364
  opts.on('--config JSON', String, "Policy Config JSON") do |val|
319
365
  options['config'] = JSON.parse(val.to_s)
@@ -342,7 +388,7 @@ class Morpheus::Cli::PoliciesCommand
342
388
  # support -O OPTION switch on top of --payload
343
389
  payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
344
390
  else
345
- group, cloud, user = nil, nil, nil
391
+ group, cloud, user, role = nil, nil, nil, nil
346
392
  if options[:group]
347
393
  group = find_group_by_name_or_id(options[:group])
348
394
  elsif options[:cloud]
@@ -350,6 +396,9 @@ class Morpheus::Cli::PoliciesCommand
350
396
  elsif options[:user]
351
397
  user = find_user_by_username_or_id(nil, options[:user])
352
398
  return 1 if user.nil?
399
+ elsif options[:role]
400
+ role = find_role_by_name_or_id(nil, options[:role])
401
+ return 1 if role.nil?
353
402
  end
354
403
 
355
404
  # merge -O options into normally parsed options
@@ -402,7 +451,21 @@ class Morpheus::Cli::PoliciesCommand
402
451
  if user
403
452
  payload['policy']['refType'] = 'User'
404
453
  payload['policy']['refId'] = user['id']
454
+ elsif role
455
+ payload['policy']['refType'] = 'Role'
456
+ payload['policy']['refId'] = role['id']
405
457
  end
458
+
459
+ # Apply To aach user (Role only)
460
+ if payload['policy']['refType'] == 'Role'
461
+ if options['eachUser'] != nil
462
+ payload['policy']['eachUser'] = ['true','on'].include?(options['eachUser'].to_s)
463
+ else
464
+ #ref_apply_dropdown = [{'name' => 'Apply cumulatively to all users in role', 'value' => ''}, {'name' => "Apply individually to each user in role", 'value' => 'user'}]
465
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'eachUser', 'fieldLabel' => 'Apply individually to each user in role', 'type' => 'checkbox', 'required' => false, 'defaultValue' => false, 'Description' => 'Apply individually to each user in role. The default is to apply cumulatively to all users in the role.'}], options)
466
+ payload['policy']['eachUser'] = ['true','on'].include?(v_prompt['eachUser'].to_s)
467
+ end
468
+ end
406
469
 
407
470
  # Name (this is not even used at the moment!)
408
471
  if options['name']
@@ -527,6 +590,9 @@ class Morpheus::Cli::PoliciesCommand
527
590
  options['accounts'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
528
591
  end
529
592
  end
593
+ opts.on('--each-user [on|off]', String, "Apply individually to each user in role, for use with policy scoped by role.") do |val|
594
+ options['eachUser'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s.empty?
595
+ end
530
596
  opts.on('--enabled [on|off]', String, "Can be used to disable a policy") do |val|
531
597
  options['enabled'] = val.to_s == 'on' || val.to_s == 'true'
532
598
  end
@@ -568,11 +634,12 @@ class Morpheus::Cli::PoliciesCommand
568
634
 
569
635
  if params.empty?
570
636
  print_error Morpheus::Terminal.angry_prompt
571
- puts_error "Specify atleast one option to update\n#{optparse}"
637
+ puts_error "Specify at least one option to update\n#{optparse}"
572
638
  return 1
573
639
  end
574
640
  payload['policy'].deep_merge!(params)
575
641
 
642
+
576
643
  # Config
577
644
  if options['config']
578
645
  payload['policy']['config'] = options['config']
@@ -35,7 +35,7 @@ class Morpheus::Cli::PreseedScriptsCommand
35
35
  options = {}
36
36
  optparse = Morpheus::Cli::OptionParser.new do |opts|
37
37
  opts.banner = subcommand_usage()
38
- build_common_options(opts, options, [:list, :json, :dry_run])
38
+ build_common_options(opts, options, [:list, :json, :dry_run, :remote])
39
39
  end
40
40
  optparse.parse!(args)
41
41
  connect(options)
@@ -229,7 +229,7 @@ class Morpheus::Cli::PreseedScriptsCommand
229
229
  #puts "parsed params is : #{params.inspect}"
230
230
  params = params.select {|k,v| params[k].to_s != "" }
231
231
  if params.empty?
232
- print_red_alert "Specify atleast one option to update"
232
+ print_red_alert "Specify at least one option to update"
233
233
  puts optparse
234
234
  return 1
235
235
  end
@@ -86,7 +86,7 @@ EOT
86
86
  print reset
87
87
  if @appliance_name
88
88
  #unless appliances.keys.size == 1
89
- print cyan, "\n# => Currently using remote #{@appliance_name}\n", reset
89
+ print cyan, "\n# => #{@appliance_name} is the current remote appliance\n", reset
90
90
  #end
91
91
  else
92
92
  print "\n# => No current remote appliance, see `remote use`\n", reset
@@ -418,7 +418,7 @@ EOT
418
418
 
419
419
  if params.empty?
420
420
  print_error Morpheus::Terminal.angry_prompt
421
- puts_error "Specify atleast one option to update"
421
+ puts_error "Specify at least one option to update"
422
422
  puts_error optparse
423
423
  return 1
424
424
  end
@@ -447,6 +447,9 @@ EOT
447
447
  options = {}
448
448
  optparse = Morpheus::Cli::OptionParser.new do |opts|
449
449
  opts.banner = subcommand_usage("[name]")
450
+ opts.on( '-u', '--url', "Print only the url." ) do
451
+ options[:url_only] = true
452
+ end
450
453
  build_common_options(opts, options, [:json,:csv, :fields, :quiet])
451
454
  end
452
455
  optparse.parse!(args)
@@ -485,6 +488,10 @@ EOT
485
488
  return
486
489
  end
487
490
 
491
+ if options[:url_only]
492
+ print cyan, appliance[:host],"\n",reset
493
+ return 0
494
+ end
488
495
  # expando
489
496
  # appliance = OStruct.new(appliance)
490
497
 
@@ -494,9 +501,9 @@ EOT
494
501
 
495
502
  if appliance[:active]
496
503
  # print_h1 "Current Remote Appliance: #{appliance[:name]}"
497
- print_h1 "Remote Appliance: #{appliance[:name]}", [], options
504
+ print_h1 "Morpheus Appliance", [], options
498
505
  else
499
- print_h1 "Remote Appliance: #{appliance[:name]}", [], options
506
+ print_h1 "Morpheus Appliance", [], options
500
507
  end
501
508
  print cyan
502
509
  description_cols = {
@@ -521,7 +528,7 @@ EOT
521
528
 
522
529
  if appliance[:active]
523
530
  # print cyan
524
- print cyan, "# => This is the current appliance.", reset, "\n\n"
531
+ print cyan, "# => #{appliance[:name]} is the current remote appliance.", reset, "\n\n"
525
532
  end
526
533
 
527
534
  return 0
@@ -637,46 +644,37 @@ EOT
637
644
  def current(args)
638
645
  options = {}
639
646
  name_only = false
647
+ url_only = false
640
648
  optparse = Morpheus::Cli::OptionParser.new do|opts|
641
649
  opts.banner = subcommand_usage()
642
650
  opts.on( '-n', '--name', "Print only the name." ) do
643
651
  name_only = true
644
652
  end
653
+ opts.on( '-u', '--url', "Print only the url." ) do
654
+ url_only = true
655
+ end
645
656
  build_common_options(opts, options, [])
646
657
  opts.footer = "Print details about the current remote appliance." +
647
658
  "The default behavior is the same as 'remote get current'."
648
659
  end
649
660
  optparse.parse!(args)
650
661
 
651
- if name_only
652
- return print_current(args)
653
- else
654
- return _get("current", {})
662
+ if !@appliance_name
663
+ print yellow, "No current appliance, see `remote use`\n", reset
664
+ return 1
655
665
  end
656
666
 
657
- if @appliance_name
667
+ if name_only
658
668
  print cyan, @appliance_name,"\n",reset
669
+ return 0
670
+ elsif url_only
671
+ print cyan, @appliance_url,"\n",reset
672
+ return 0
659
673
  else
660
- print yellow, "No active appliance, see `remote use`\n", reset
661
- return false
674
+ return _get("current", options)
662
675
  end
663
- end
664
676
 
665
- def print_current(args)
666
- options = {}
667
- optparse = Morpheus::Cli::OptionParser.new do|opts|
668
- opts.banner = subcommand_usage()
669
- build_common_options(opts, options, [])
670
- opts.footer = "Print the name of the current remote appliance"
671
- end
672
- optparse.parse!(args)
673
-
674
- if @appliance_name
675
- print cyan, @appliance_name,"\n",reset
676
- else
677
- print yellow, "No active appliance, see `remote use`\n", reset
678
- return false
679
- end
677
+
680
678
  end
681
679
 
682
680
  # this is a wizard that walks through the /api/setup controller
@@ -0,0 +1,594 @@
1
+ require 'optparse'
2
+ require 'morpheus/cli/cli_command'
3
+ require 'json'
4
+
5
+ class Morpheus::Cli::ReportsCommand
6
+ include Morpheus::Cli::CliCommand
7
+ set_command_name :reports
8
+
9
+ def initialize()
10
+ # @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
11
+ end
12
+
13
+ def connect(opts)
14
+ @api_client = establish_remote_appliance_connection(opts)
15
+ @reports_interface = @api_client.reports
16
+ end
17
+
18
+ register_subcommands :list, :get, :run, :view, :export, :remove, :types
19
+
20
+ def handle(args)
21
+ handle_subcommand(args)
22
+ end
23
+
24
+ def list(args)
25
+ options = {}
26
+ params = {}
27
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
28
+ opts.banner = subcommand_usage()
29
+ opts.on( '--type CODE', String, "Report Type code(s)" ) do |val|
30
+ params['reportType'] = val.to_s.split(",").compact.collect {|it| it.strip }
31
+ end
32
+ build_common_options(opts, options, [:list, :json, :dry_run, :remote])
33
+ opts.footer = "List report history."
34
+ end
35
+ optparse.parse!(args)
36
+ connect(options)
37
+ begin
38
+ params.merge!(parse_list_options(options))
39
+
40
+ @reports_interface.setopts(options)
41
+ if options[:dry_run]
42
+ print_dry_run @reports_interface.dry.list(params)
43
+ return
44
+ end
45
+
46
+ json_response = @reports_interface.list(params)
47
+ if options[:json]
48
+ puts as_json(json_response, options)
49
+ return 0
50
+ end
51
+ report_results = json_response['reportResults']
52
+
53
+ title = "Morpheus Report History"
54
+ subtitles = []
55
+ if params['type']
56
+ subtitles << "Type: #{params[:type]}".strip
57
+ end
58
+ subtitles += parse_list_subtitles(options)
59
+ print_h1 title, subtitles
60
+
61
+ if report_results.empty?
62
+ print cyan, "No report results found", reset, "\n"
63
+ else
64
+ columns = {
65
+ "ID" => 'id',
66
+ "TITLE" => lambda {|it| truncate_string(it['reportTitle'], 50) },
67
+ "FILTERS" => lambda {|it| truncate_string(it['filterTitle'], 30) },
68
+ "REPORT TYPE" => lambda {|it| it['type'].is_a?(Hash) ? it['type']['name'] : it['type'] },
69
+ "DATE RUN" => lambda {|it| format_local_dt(it['dateCreated']) },
70
+ "CREATED BY" => lambda {|it| it['createdBy'].is_a?(Hash) ? it['createdBy']['username'] : it['createdBy'] },
71
+ "STATUS" => lambda {|it| format_report_status(it) }
72
+ }
73
+ # custom pretty table columns ...
74
+ if options[:include_fields]
75
+ columns = options[:include_fields]
76
+ end
77
+ print as_pretty_table(report_results, columns, options)
78
+ print reset
79
+ print_results_pagination(json_response)
80
+ end
81
+
82
+ print reset,"\n"
83
+ return 0
84
+ rescue RestClient::Exception => e
85
+ print_rest_exception(e, options)
86
+ exit 1
87
+ end
88
+ end
89
+
90
+ def get(args)
91
+ original_args = args.dup
92
+ options = {}
93
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
94
+ opts.banner = subcommand_usage("[id]")
95
+ opts.on('--refresh [SECONDS]', String, "Refresh until status is ready,failed. Default interval is 5 seconds.") do |val|
96
+ options[:refresh_until_status] ||= "ready,failed"
97
+ if !val.to_s.empty?
98
+ options[:refresh_interval] = val.to_f
99
+ end
100
+ end
101
+ opts.on('--refresh-until STATUS', String, "Refresh until a specified status is reached.") do |val|
102
+ options[:refresh_until_status] = val.to_s.downcase
103
+ end
104
+ opts.on('--rows', '--rows', "Print Report Data rows too.") do
105
+ options[:show_data_rows] = true
106
+ end
107
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :outfile, :dry_run, :remote])
108
+ opts.footer = "Get details about a report result." + "\n"
109
+ + "[id] is required. This is the id of the report result."
110
+ end
111
+ optparse.parse!(args)
112
+ if args.count != 1
113
+ print_error Morpheus::Terminal.angry_prompt
114
+ puts_error "#{command_name} missing argument: [id]\n#{optparse}"
115
+ return 1
116
+ end
117
+ connect(options)
118
+ begin
119
+ @reports_interface.setopts(options)
120
+ if options[:dry_run]
121
+ print_dry_run @reports_interface.dry.get(args[0].to_i)
122
+ return 0
123
+ end
124
+
125
+ report_result = find_report_result_by_id(args[0])
126
+ return 1 if report_result.nil?
127
+ json_response = {'reportResult' => report_result} # skip redundant request
128
+ # json_response = @reports_interface.get(report['id'])
129
+ #report_result = json_response['reportResult']
130
+
131
+ # if options[:json]
132
+ # puts as_json(json_response, options)
133
+ # return 0
134
+ # end
135
+ # render_with_format() handles json,yaml,csv,outfile,etc
136
+ render_result = render_with_format(json_response, options, 'reportResult')
137
+ if render_result
138
+ #return render_result
139
+ else
140
+ print_h1 "Morpheus Report Details"
141
+ print cyan
142
+
143
+ description_cols = {
144
+ "ID" => 'id',
145
+ "Title" => lambda {|it| it['reportTitle'] },
146
+ "Filters" => lambda {|it| it['filterTitle'] },
147
+ "Report Type" => lambda {|it| it['type'].is_a?(Hash) ? it['type']['name'] : it['type'] },
148
+ "Date Run" => lambda {|it| format_local_dt(it['dateCreated']) },
149
+ "Created By" => lambda {|it| it['createdBy'].is_a?(Hash) ? it['createdBy']['username'] : it['createdBy'] },
150
+ "Status" => lambda {|it| format_report_status(it) }
151
+ }
152
+ print_description_list(description_cols, report_result)
153
+
154
+ # todo:
155
+ # 1. format raw output better.
156
+ # 2. write rendering methods for all the various types...
157
+ if options[:show_data_rows]
158
+ print_h2 "Report Data Rows"
159
+ print cyan
160
+ if report_result['rows']
161
+ # report_result['rows'].each_with_index do |row, index|
162
+ # print "#{index}: ", row, "\n"
163
+ # end
164
+ term_width = current_terminal_width()
165
+ data_width = term_width.to_i - 30
166
+ if data_width < 0
167
+ data_wdith = 10
168
+ end
169
+ puts as_pretty_table(report_result['rows'], {
170
+ "ID" => lambda {|it| it['id'] },
171
+ "SECTION" => lambda {|it| it['section'] },
172
+ "DATA" => lambda {|it| truncate_string(it['data'], data_width) }
173
+ })
174
+
175
+ else
176
+ print yellow, "No report data found.", reset, "\n"
177
+ end
178
+ end
179
+
180
+ print reset,"\n"
181
+ end
182
+
183
+ # refresh until a status is reached
184
+ if options[:refresh_until_status]
185
+ if options[:refresh_interval].nil? || options[:refresh_interval].to_f < 0
186
+ options[:refresh_interval] = 5
187
+ end
188
+ statuses = options[:refresh_until_status].to_s.downcase.split(",").collect {|s| s.strip }.select {|s| !s.to_s.empty? }
189
+ if !statuses.include?(report_result['status'])
190
+ print cyan, "Refreshing in #{options[:refresh_interval]} seconds"
191
+ sleep_with_dots(options[:refresh_interval])
192
+ print "\n"
193
+ get(original_args)
194
+ end
195
+ end
196
+ return 0
197
+ rescue RestClient::Exception => e
198
+ print_rest_exception(e, options)
199
+ return 1
200
+ end
201
+ end
202
+
203
+ def run(args)
204
+ params = {}
205
+ do_refresh = true
206
+ options = {:options => {}}
207
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
208
+ opts.banner = subcommand_usage("[type] [options]")
209
+ opts.on( '--type CODE', String, "Report Type code" ) do |val|
210
+ options[:options]['type'] = val
211
+ end
212
+ # opts.on( '--title TITLE', String, "Title for the report" ) do |val|
213
+ # options[:options]['reportTitle'] = val
214
+ # end
215
+ opts.on(nil, '--no-refresh', "Do not refresh until finished" ) do
216
+ do_refresh = false
217
+ end
218
+ opts.on('--rows', '--rows', "Print Report Data rows too.") do
219
+ options[:show_data_rows] = true
220
+ end
221
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
222
+ opts.footer = "Run a report to generate a new result." + "\n" +
223
+ "[type] is required. This is code of the report type."
224
+ end
225
+ optparse.parse!(args)
226
+ if args.count > 1
227
+ raise_command_error "wrong number of arguments, expected 0-1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
228
+ end
229
+ if args[0]
230
+ options[:options]['type'] = args[0]
231
+ end
232
+ connect(options)
233
+ begin
234
+
235
+ # construct payload
236
+ passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
237
+ payload = nil
238
+ if options[:payload]
239
+ payload = options[:payload]
240
+ payload.deep_merge!({'report' => passed_options}) unless passed_options.empty?
241
+ else
242
+ # prompt for resource folder options
243
+ payload = {
244
+ 'report' => {
245
+ }
246
+ }
247
+ # allow arbitrary -O options
248
+ payload.deep_merge!({'report' => passed_options}) unless passed_options.empty?
249
+
250
+ # Report Type
251
+ @all_report_types ||= @reports_interface.types({max: 1000})['reportTypes'] || []
252
+ report_types_dropdown = @all_report_types.collect {|it| {"name" => it["name"], "value" => it["code"]} }
253
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'fieldLabel' => 'Report Type', 'type' => 'select', 'selectOptions' => report_types_dropdown, 'required' => true}], options[:options], @api_client)
254
+ payload['report']['type'] = v_prompt['type']
255
+ # convert name/code/id to code
256
+ report_type = find_report_type_by_name_or_code_id(payload['report']['type'])
257
+ return 1 if report_type.nil?
258
+ payload['report']['type'] = report_type['code']
259
+
260
+ # Report Types need to tell us what the available filters are...
261
+
262
+ # Start Date
263
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'startDate', 'fieldLabel' => 'Start Date', 'type' => 'text', 'required' => false}], options[:options])
264
+ payload['report']['startDate'] = v_prompt['startDate'] unless v_prompt['startDate'].to_s.empty?
265
+
266
+ # End Date
267
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'endDate', 'fieldLabel' => 'End Date', 'type' => 'text', 'required' => false}], options[:options])
268
+ payload['report']['endDate'] = v_prompt['endDate'] unless v_prompt['endDate'].to_s.empty?
269
+
270
+ end
271
+
272
+ @reports_interface.setopts(options)
273
+ if options[:dry_run]
274
+ print_dry_run @reports_interface.dry.create(payload)
275
+ return 0
276
+ end
277
+ json_response = @reports_interface.create(payload)
278
+ if options[:json]
279
+ puts as_json(json_response, options)
280
+ return 0
281
+ end
282
+
283
+ print_green_success "Created report result #{json_response['reportResult']['id']}"
284
+ print_args = [json_response['reportResult']['id']]
285
+ print_args << "--refresh" if do_refresh
286
+ print_args << "--rows" if options[:show_data_rows]
287
+ get(print_args)
288
+ return 0
289
+ rescue RestClient::Exception => e
290
+ print_rest_exception(e, options)
291
+ exit 1
292
+ end
293
+ end
294
+
295
+ def view(args)
296
+ options = {}
297
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
298
+ opts.banner = subcommand_usage("[id]")
299
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
300
+ opts.footer = "View a report result in a web browser" + "\n" +
301
+ "[id] is required. This is id of the report result."
302
+ end
303
+ optparse.parse!(args)
304
+ if args.count != 1
305
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
306
+ end
307
+ connect(options)
308
+ begin
309
+ report_result = find_report_result_by_id(args[0])
310
+ return 1 if report_result.nil?
311
+
312
+ link = "#{@appliance_url}/login/oauth-redirect?access_token=#{@access_token}\\&redirectUri=/operations/reports/#{report_result['type']['code']}/reportResults/#{report_result['id']}"
313
+
314
+ open_command = nil
315
+ if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
316
+ open_command = "start #{link}"
317
+ elsif RbConfig::CONFIG['host_os'] =~ /darwin/
318
+ open_command = "open #{link}"
319
+ elsif RbConfig::CONFIG['host_os'] =~ /linux|bsd/
320
+ open_command = "xdg-open #{link}"
321
+ end
322
+
323
+ if options[:dry_run]
324
+ puts "system: #{open_command}"
325
+ return 0
326
+ end
327
+
328
+ system(open_command)
329
+
330
+ return 0
331
+ rescue RestClient::Exception => e
332
+ print_rest_exception(e, options)
333
+ exit 1
334
+ end
335
+ end
336
+
337
+ def export(args)
338
+ params = {}
339
+ report_format = 'json'
340
+ options = {}
341
+ outfile = nil
342
+ do_overwrite = false
343
+ do_mkdir = false
344
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
345
+ opts.banner = subcommand_usage("[id] [file]")
346
+ build_common_options(opts, options, [:dry_run, :remote])
347
+ opts.on( '--format VALUE', String, "Report Format for exported file, json or csv. Default is json." ) do |val|
348
+ report_format = val
349
+ end
350
+ opts.on( '-f', '--force', "Overwrite existing [local-file] if it exists." ) do
351
+ do_overwrite = true
352
+ # do_mkdir = true
353
+ end
354
+ opts.on( '-p', '--mkdir', "Create missing directories for [local-file] if they do not exist." ) do
355
+ do_mkdir = true
356
+ end
357
+ opts.footer = "Export a report result as json or csv." + "\n" +
358
+ "[id] is required. This is id of the report result." + "\n" +
359
+ "[file] is required. This is local destination for the downloaded file."
360
+ end
361
+ optparse.parse!(args)
362
+ if args.count != 2
363
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
364
+ end
365
+ connect(options)
366
+ begin
367
+ report_result = find_report_result_by_id(args[0])
368
+ return 1 if report_result.nil?
369
+
370
+ outfile = args[1]
371
+ outfile = File.expand_path(outfile)
372
+
373
+ if Dir.exists?(outfile)
374
+ print_red_alert "[file] is invalid. It is the name of an existing directory: #{outfile}"
375
+ return 1
376
+ end
377
+ destination_dir = File.dirname(outfile)
378
+ if !Dir.exists?(destination_dir)
379
+ if do_mkdir
380
+ print cyan,"Creating local directory #{destination_dir}",reset,"\n"
381
+ FileUtils.mkdir_p(destination_dir)
382
+ else
383
+ print_red_alert "[file] is invalid. Directory not found: #{destination_dir}"
384
+ return 1
385
+ end
386
+ end
387
+ if File.exists?(outfile)
388
+ if do_overwrite
389
+ # uhh need to be careful wih the passed filepath here..
390
+ # don't delete, just overwrite.
391
+ # File.delete(outfile)
392
+ else
393
+ print_error Morpheus::Terminal.angry_prompt
394
+ puts_error "[file] is invalid. File already exists: #{outfile}", "Use -f to overwrite the existing file."
395
+ # puts_error optparse
396
+ return 1
397
+ end
398
+ end
399
+
400
+ @reports_interface.setopts(options)
401
+ if options[:dry_run]
402
+ print_dry_run @reports_interface.dry.export(report_result['id'], outfile, params, report_format)
403
+ return 0
404
+ end
405
+ json_response = @reports_interface.export(report_result['id'], outfile, params, report_format)
406
+ print_green_success "Exported report result #{report_result['id']} to file #{outfile}"
407
+ return 0
408
+ rescue RestClient::Exception => e
409
+ print_rest_exception(e, options)
410
+ exit 1
411
+ end
412
+ end
413
+
414
+ def remove(args)
415
+ options = {}
416
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
417
+ opts.banner = subcommand_usage("[id]")
418
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
419
+ opts.footer = "Delete a report result." + "\n" +
420
+ "[id] is required. This is id of the report result."
421
+ end
422
+ optparse.parse!(args)
423
+ if args.count != 1
424
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
425
+ end
426
+ connect(options)
427
+ begin
428
+ report_result = find_report_result_by_id(args[0])
429
+ return 1 if report_result.nil?
430
+
431
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the report result: #{report_result['id']}?")
432
+ return 9, "aborted command"
433
+ end
434
+
435
+ @reports_interface.setopts(options)
436
+ if options[:dry_run]
437
+ print_dry_run @reports_interface.dry.destroy(report_result['id'])
438
+ return
439
+ end
440
+ json_response = @reports_interface.destroy(report_result['id'])
441
+ if options[:json]
442
+ puts as_json(json_response, options)
443
+ return 0
444
+ end
445
+ print_green_success "Deleted report result #{report_result['id']}"
446
+ #list([])
447
+ return 0
448
+ rescue RestClient::Exception => e
449
+ print_rest_exception(e, options)
450
+ exit 1
451
+ end
452
+ end
453
+
454
+ def types(args)
455
+ options = {}
456
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
457
+ opts.banner = subcommand_usage()
458
+ build_common_options(opts, options, [:list, :json, :dry_run, :remote])
459
+ opts.footer = "List report types."
460
+ end
461
+ optparse.parse!(args)
462
+ connect(options)
463
+ begin
464
+ params = {}
465
+ params.merge!(parse_list_options(options))
466
+
467
+ @reports_interface.setopts(options)
468
+ if options[:dry_run]
469
+ print_dry_run @reports_interface.dry.types(params)
470
+ return
471
+ end
472
+
473
+ json_response = @reports_interface.types(params)
474
+ if options[:json]
475
+ print JSON.pretty_generate(json_response)
476
+ print "\n"
477
+ return
478
+ end
479
+
480
+
481
+ title = "Morpheus Report Types"
482
+ subtitles = []
483
+ subtitles += parse_list_subtitles(options)
484
+ print_h1 title, subtitles
485
+
486
+ report_types = json_response['reportTypes']
487
+
488
+ if report_types.empty?
489
+ print yellow,"No report types found.",reset,"\n"
490
+ else
491
+ columns = {
492
+ "NAME" => 'name',
493
+ "CODE" => 'code'
494
+ }
495
+ # custom pretty table columns ...
496
+ if options[:include_fields]
497
+ columns = options[:include_fields]
498
+ end
499
+ print as_pretty_table(report_types, columns, options)
500
+ print reset
501
+ if json_response['meta']
502
+ print_results_pagination(json_response)
503
+ else
504
+ print_results_pagination({'meta'=>{'total'=>(report_types.size),'size'=>report_types.size,'max'=>(params['max']||25),'offset'=>(params['offset']||0)}})
505
+ end
506
+ end
507
+ print reset,"\n"
508
+ return 0
509
+ rescue RestClient::Exception => e
510
+ print_rest_exception(e, options)
511
+ exit 1
512
+ end
513
+ end
514
+
515
+
516
+ def find_report_result_by_id(id)
517
+ begin
518
+ json_response = @reports_interface.get(id.to_i)
519
+ return json_response['reportResult']
520
+ rescue RestClient::Exception => e
521
+ if e.response && e.response.code == 404
522
+ print_red_alert "Report Result not found by id #{id}"
523
+ return nil
524
+ else
525
+ raise e
526
+ end
527
+ end
528
+ end
529
+
530
+ def find_report_type_by_name_or_code_id(val)
531
+ if val.to_s =~ /\A\d{1,}\Z/
532
+ return find_report_type_by_id(val)
533
+ else
534
+ return find_report_type_by_name_or_code(val)
535
+ end
536
+ end
537
+
538
+ def find_report_type_by_id(id)
539
+ @all_report_types ||= @reports_interface.list({max: 1000})['reportTypes'] || []
540
+ report_types = @all_report_types.select { |it| id && it['id'] == id.to_i }
541
+ if report_types.empty?
542
+ print_red_alert "Report Type not found by id #{id}"
543
+ return nil
544
+ elsif report_types.size > 1
545
+ print_red_alert "#{report_types.size} report types found by id #{id}"
546
+ rows = report_types.collect do |it|
547
+ {id: it['id'], code: it['code'], name: it['name']}
548
+ end
549
+ print "\n"
550
+ puts as_pretty_table(rows, [:id, :code, :name], {color:red})
551
+ return nil
552
+ else
553
+ return report_types[0]
554
+ end
555
+ end
556
+
557
+ def find_report_type_by_name_or_code(name)
558
+ @all_report_types ||= @reports_interface.list({max: 1000})['reportTypes'] || []
559
+ report_types = @all_report_types.select { |it| name && it['code'] == name || it['name'] == name }
560
+ if report_types.empty?
561
+ print_red_alert "Report Type not found by code #{name}"
562
+ return nil
563
+ elsif report_types.size > 1
564
+ print_red_alert "#{report_types.size} report types found by code #{name}"
565
+ rows = report_types.collect do |it|
566
+ {id: it['id'], code: it['code'], name: it['name']}
567
+ end
568
+ print "\n"
569
+ puts as_pretty_table(rows, [:id, :code, :name], {color:red})
570
+ return nil
571
+ else
572
+ return report_types[0]
573
+ end
574
+ end
575
+
576
+ def format_report_status(report_result, return_color=cyan)
577
+ out = ""
578
+ status_string = report_result['status'].to_s
579
+ if status_string == 'ready'
580
+ out << "#{green}#{status_string.upcase}#{return_color}"
581
+ elsif status_string == 'requested'
582
+ out << "#{cyan}#{status_string.upcase}#{return_color}"
583
+ elsif status_string == 'generating'
584
+ out << "#{cyan}#{status_string.upcase}#{return_color}"
585
+ elsif status_string == 'failed'
586
+ out << "#{red}#{status_string.upcase}#{return_color}"
587
+ else
588
+ out << "#{yellow}#{status_string.upcase}#{return_color}"
589
+ end
590
+ out
591
+ end
592
+
593
+ end
594
+