morpheus-cli 3.6.28 → 3.6.29

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