morpheus-cli 5.0.0 → 5.0.1

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +12 -0
  4. data/lib/morpheus/api/billing_interface.rb +1 -0
  5. data/lib/morpheus/api/deploy_interface.rb +1 -1
  6. data/lib/morpheus/api/deployments_interface.rb +20 -1
  7. data/lib/morpheus/api/forgot_password_interface.rb +17 -0
  8. data/lib/morpheus/api/instances_interface.rb +7 -0
  9. data/lib/morpheus/api/search_interface.rb +13 -0
  10. data/lib/morpheus/api/servers_interface.rb +7 -0
  11. data/lib/morpheus/api/usage_interface.rb +18 -0
  12. data/lib/morpheus/cli.rb +4 -1
  13. data/lib/morpheus/cli/cli_command.rb +26 -9
  14. data/lib/morpheus/cli/commands/standard/curl_command.rb +3 -5
  15. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
  16. data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
  17. data/lib/morpheus/cli/deploy.rb +199 -90
  18. data/lib/morpheus/cli/deployments.rb +341 -28
  19. data/lib/morpheus/cli/deploys.rb +206 -41
  20. data/lib/morpheus/cli/error_handler.rb +7 -0
  21. data/lib/morpheus/cli/forgot_password.rb +133 -0
  22. data/lib/morpheus/cli/health_command.rb +2 -2
  23. data/lib/morpheus/cli/hosts.rb +169 -32
  24. data/lib/morpheus/cli/instances.rb +83 -32
  25. data/lib/morpheus/cli/invoices_command.rb +33 -16
  26. data/lib/morpheus/cli/logs_command.rb +9 -6
  27. data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -2
  28. data/lib/morpheus/cli/mixins/print_helper.rb +0 -21
  29. data/lib/morpheus/cli/mixins/provisioning_helper.rb +24 -4
  30. data/lib/morpheus/cli/option_types.rb +266 -17
  31. data/lib/morpheus/cli/remote.rb +35 -10
  32. data/lib/morpheus/cli/reports_command.rb +99 -30
  33. data/lib/morpheus/cli/search_command.rb +182 -0
  34. data/lib/morpheus/cli/setup.rb +1 -1
  35. data/lib/morpheus/cli/shell.rb +33 -11
  36. data/lib/morpheus/cli/tasks.rb +20 -21
  37. data/lib/morpheus/cli/usage_command.rb +64 -11
  38. data/lib/morpheus/cli/version.rb +1 -1
  39. data/lib/morpheus/cli/virtual_images.rb +280 -199
  40. data/lib/morpheus/cli/whoami.rb +6 -6
  41. data/lib/morpheus/cli/workflows.rb +33 -40
  42. data/lib/morpheus/formatters.rb +22 -0
  43. data/lib/morpheus/terminal.rb +6 -2
  44. metadata +7 -2
@@ -23,6 +23,8 @@ class Morpheus::Cli::Remote
23
23
 
24
24
  set_default_subcommand :list
25
25
 
26
+ set_subcommands_hidden :setup # this is going away too
27
+
26
28
  def initialize()
27
29
  @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
28
30
  end
@@ -595,7 +597,7 @@ EOT
595
597
  if args.count != 0
596
598
  raise_command_error "wrong number of arguments, expected 0-1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
597
599
  end
598
- connect(options) # needed?
600
+ #connect(options) # needed?
599
601
  _check_all_appliances(options)
600
602
  end
601
603
 
@@ -826,6 +828,12 @@ EOT
826
828
  options[:do_offline] = true
827
829
  end
828
830
  build_common_options(opts, options, [:json,:yaml,:csv,:fields, :quiet])
831
+ opts.footer = <<-EOT
832
+ Print details about the a remote appliance.
833
+ [name] is optional. This is the name of a remote.
834
+ By default, the current appliance is used.
835
+ Returns an error if the specified remote is not found, or there is no current remote.
836
+ EOT
829
837
  end
830
838
  optparse.parse!(args)
831
839
  id_list = nil
@@ -843,7 +851,16 @@ EOT
843
851
 
844
852
  def _get(appliance_name, options)
845
853
  exit_code, err = 0, nil
846
-
854
+ if appliance_name == 'current'
855
+ current_appliance = ::Morpheus::Cli::Remote.load_active_remote()
856
+ if current_appliance.nil?
857
+ #raise_command_error "No current appliance, see the command `remote use`"
858
+ unless options[:quiet]
859
+ print yellow, "No current appliance, see the command `remote use`", reset, "\n"
860
+ end
861
+ return 1, "No current appliance"
862
+ end
863
+ end
847
864
  appliance = load_remote_by_name(appliance_name)
848
865
  appliance_name = appliance[:name]
849
866
  appliance_url = appliance[:url]
@@ -1039,15 +1056,14 @@ EOT
1039
1056
  end
1040
1057
  build_common_options(opts, options, [:quiet])
1041
1058
  opts.footer = <<-EOT
1042
- [name] is required. This is the name of a remote, see the command `remote list`.
1059
+ [name] is required. This is the name of a remote to begin using.
1043
1060
  Start using a remote, making it the active (current) remote appliance.
1044
1061
  This switches the remote context of your client configuration for all subsequent commands.
1045
- So rely on 'remote use' with caution.
1046
1062
  It is important to always be aware of the context your commands are running in.
1047
1063
  The command `remote current` will return the current remote information.
1048
- Also, instead of using an active remote, the -r option can be specified in your commands.
1064
+ Instead of using an active remote, the -r option can be specified with each command.
1049
1065
 
1050
- It is recommeneded to set a custom prompt to show the remote name.
1066
+ It is recommeneded to set a custom prompt to show the current remote name.
1051
1067
  For example, add the following to your .morpheusrc file:
1052
1068
 
1053
1069
  # set your shell prompt to display the current username and remote
@@ -1155,13 +1171,22 @@ EOT
1155
1171
  end
1156
1172
  optparse.parse!(args)
1157
1173
  verify_args!(args:args, count:0, optparse:optparse)
1158
- connect(options)
1174
+ #connect(options)
1175
+
1176
+ current_appliance = ::Morpheus::Cli::Remote.load_active_remote()
1177
+ if current_appliance.nil?
1178
+ #raise_command_error "No current appliance, see the command `remote use`"
1179
+ unless options[:quiet]
1180
+ print yellow, "No current appliance, see the command `remote use`", reset, "\n"
1181
+ end
1182
+ return 1, "No current appliance"
1183
+ end
1159
1184
 
1160
1185
  # this does the same thing
1161
1186
  #return _get("current", options)
1162
-
1163
- # appliance = load_remote_by_name("current")
1164
- appliance = @remote_appliance
1187
+ appliance = current_appliance
1188
+ #appliance = load_remote_by_name("current")
1189
+ #appliance = @remote_appliance
1165
1190
  exit_code, err = 0, nil
1166
1191
  if appliance.nil?
1167
1192
  raise_command_error "no current remote appliance, see command `remote add`."
@@ -15,7 +15,10 @@ class Morpheus::Cli::ReportsCommand
15
15
  @reports_interface = @api_client.reports
16
16
  end
17
17
 
18
- register_subcommands :list, :get, :run, :view, :export, :remove, :types
18
+ register_subcommands :list, :get, :run, :view, :export, :remove
19
+ register_subcommands :'list-types' => :list_types
20
+ register_subcommands :'get-type' => :get_type
21
+ alias_subcommand :types, :'list-types'
19
22
 
20
23
  def default_refresh_interval
21
24
  5
@@ -271,10 +274,10 @@ class Morpheus::Cli::ReportsCommand
271
274
 
272
275
  # Report Types tell us what the available filters are...
273
276
  report_option_types = report_type['optionTypes'] || []
274
- report_option_types = report_option_types.collect {|it|
275
- it['fieldContext'] = nil
276
- it
277
- }
277
+ # report_option_types = report_option_types.collect {|it|
278
+ # it['fieldContext'] = nil
279
+ # it
280
+ # }
278
281
  # pluck out optionTypes like the UI does..
279
282
  metadata_option_type = nil
280
283
  if report_option_types.find {|it| it['fieldName'] == 'metadata' }
@@ -284,6 +287,13 @@ class Morpheus::Cli::ReportsCommand
284
287
  v_prompt = Morpheus::Cli::OptionTypes.prompt(report_option_types, options[:options], @api_client)
285
288
  payload.deep_merge!({'report' => v_prompt}) unless v_prompt.empty?
286
289
 
290
+ # strip out fieldContext: 'config' please
291
+ # just report.startDate instead of report.config.startDate
292
+ if payload['report']['config'].is_a?(Hash)
293
+ payload['report']['config']
294
+ payload['report'].deep_merge!(payload['report'].delete('config'))
295
+ end
296
+
287
297
  if metadata_option_type
288
298
  if !options[:options]['metadata']
289
299
  metadata_filter = prompt_metadata(options)
@@ -466,33 +476,30 @@ class Morpheus::Cli::ReportsCommand
466
476
  end
467
477
  end
468
478
 
469
- def types(args)
479
+ def list_types(args)
480
+ params = {}
470
481
  options = {}
471
482
  optparse = Morpheus::Cli::OptionParser.new do |opts|
472
483
  opts.banner = subcommand_usage()
473
- build_common_options(opts, options, [:list, :json, :dry_run, :remote])
484
+ build_standard_list_options(opts, options)
474
485
  opts.footer = "List report types."
475
486
  end
476
487
  optparse.parse!(args)
488
+ if args.count > 0
489
+ options[:phrase] = args.join(" ")
490
+ end
477
491
  connect(options)
478
- begin
479
- params = {}
480
- params.merge!(parse_list_options(options))
481
-
482
- @reports_interface.setopts(options)
483
- if options[:dry_run]
484
- print_dry_run @reports_interface.dry.types(params)
485
- return
486
- end
487
-
488
- json_response = @reports_interface.types(params)
489
- if options[:json]
490
- print JSON.pretty_generate(json_response)
491
- print "\n"
492
- return
493
- end
494
-
492
+ params.merge!(parse_list_options(options))
493
+
494
+ @reports_interface.setopts(options)
495
+ if options[:dry_run]
496
+ print_dry_run @reports_interface.dry.types(params)
497
+ return
498
+ end
495
499
 
500
+ json_response = @reports_interface.types(params)
501
+ report_types = json_response['reportTypes']
502
+ render_response(json_response, options, 'reportTypes') do
496
503
  title = "Morpheus Report Types"
497
504
  subtitles = []
498
505
  subtitles += parse_list_subtitles(options)
@@ -520,13 +527,75 @@ class Morpheus::Cli::ReportsCommand
520
527
  end
521
528
  end
522
529
  print reset,"\n"
523
- return 0
524
- rescue RestClient::Exception => e
525
- print_rest_exception(e, options)
526
- exit 1
530
+ end
531
+ if report_types.empty?
532
+ return 1, "no report types found"
533
+ else
534
+ return 0, nil
527
535
  end
528
536
  end
529
537
 
538
+ def get_type(args)
539
+ params = {}
540
+ options = {}
541
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
542
+ opts.banner = subcommand_usage()
543
+ build_standard_get_options(opts, options)
544
+ opts.footer = <<-EOT
545
+ Get report type
546
+ [name] is required. This is the name of a report type
547
+ EOT
548
+ end
549
+ optparse.parse!(args)
550
+ connect(options)
551
+ verify_args!(args:args, optparse:optparse, min:1)
552
+ params.merge!(parse_query_options(options))
553
+ params['name'] = args.join(" ")
554
+ @reports_interface.setopts(options)
555
+ if options[:dry_run]
556
+ print_dry_run @reports_interface.dry.types(params)
557
+ return
558
+ end
559
+
560
+ # json_response = @reports_interface.types(params)
561
+ # api does not have a show() action right now... so find by code or name only
562
+ report_type = find_report_type_by_name_or_code_id(params['name'])
563
+ return 1 if report_type.nil?
564
+
565
+ # json_response = @reports_interface.get_type(report_type['id'])
566
+ # report_type = json_response['reportType']
567
+ json_response = {'reportType' => report_type}
568
+ render_response(json_response, options, 'reportType') do
569
+ print_h1 "Report Type Details", [], options
570
+
571
+ description_cols = {
572
+ "ID" => 'id',
573
+ "Name" => 'name',
574
+ "Code" => 'code',
575
+ "Description" => 'description',
576
+ "Category" => 'category'
577
+ }
578
+ print_description_list(description_cols, report_type)
579
+
580
+ print_h2 "Option Types", options
581
+ opt_columns = [
582
+ {"ID" => lambda {|it| it['id'] } },
583
+ {"NAME" => lambda {|it| it['name'] } },
584
+ {"TYPE" => lambda {|it| it['type'] } },
585
+ {"FIELD NAME" => lambda {|it| it['fieldName'] } },
586
+ {"FIELD LABEL" => lambda {|it| it['fieldLabel'] } },
587
+ {"DEFAULT" => lambda {|it| it['defaultValue'] } },
588
+ {"REQUIRED" => lambda {|it| format_boolean it['required'] } },
589
+ ]
590
+ option_types = report_type['optionTypes']
591
+ sorted_option_types = (option_types && option_types[0] && option_types[0]['displayOrder']) ? option_types.sort { |x,y| x['displayOrder'].to_i <=> y['displayOrder'].to_i } : option_types
592
+ print as_pretty_table(sorted_option_types, opt_columns)
593
+
594
+ print reset,"\n"
595
+ end
596
+ return 0, nil
597
+
598
+ end
530
599
 
531
600
  def find_report_result_by_id(id)
532
601
  begin
@@ -551,7 +620,7 @@ class Morpheus::Cli::ReportsCommand
551
620
  end
552
621
 
553
622
  def find_report_type_by_id(id)
554
- @all_report_types ||= @reports_interface.list({max: 1000})['reportTypes'] || []
623
+ @all_report_types ||= @reports_interface.types({max: 10000})['reportTypes'] || []
555
624
  report_types = @all_report_types.select { |it| id && it['id'] == id.to_i }
556
625
  if report_types.empty?
557
626
  print_red_alert "Report Type not found by id #{id}"
@@ -570,7 +639,7 @@ class Morpheus::Cli::ReportsCommand
570
639
  end
571
640
 
572
641
  def find_report_type_by_name_or_code(name)
573
- @all_report_types ||= @reports_interface.list({max: 1000})['reportTypes'] || []
642
+ @all_report_types ||= @reports_interface.types({max: 10000})['reportTypes'] || []
574
643
  report_types = @all_report_types.select { |it| name && it['code'] == name || it['name'] == name }
575
644
  if report_types.empty?
576
645
  print_red_alert "Report Type not found by code #{name}"
@@ -0,0 +1,182 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::SearchCommand
4
+ include Morpheus::Cli::CliCommand
5
+
6
+ set_command_name :search
7
+ set_command_description "Global search for finding all types of objects"
8
+
9
+ def connect(opts)
10
+ @api_client = establish_remote_appliance_connection(opts)
11
+ @search_interface = @api_client.search
12
+ end
13
+
14
+ def handle(args)
15
+ options = {}
16
+ params = {}
17
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
18
+ opts.banner = "Usage: #{prog_name} #{command_name} [phrase]"
19
+ opts.on("-g", "--go", "Go get details for the top search result instead of printing the list.") do
20
+ options[:go] = true
21
+ end
22
+ build_standard_list_options(opts, options)
23
+ opts.footer = <<-EOT
24
+ Global search for finding all types of objects
25
+ [phrase] is required. This is the phrase to search for.
26
+ Prints the list of search results with the most relevant first.
27
+ or use the --go option to get details about the top result instead.
28
+ EOT
29
+ end
30
+ optparse.parse!(args)
31
+ connect(options)
32
+ # verify_args!(args:args, optparse:optparse, min:1)
33
+ if args.count > 0
34
+ options[:phrase] = args.join(" ")
35
+ end
36
+ if options[:phrase].to_s.empty?
37
+ raise_command_error "[phrase] is required.", args, optparse
38
+ end
39
+ params.merge!(parse_list_options(options))
40
+ @search_interface.setopts(options)
41
+ if options[:dry_run]
42
+ print_dry_run @search_interface.dry.list(params)
43
+ return
44
+ end
45
+ json_response = @search_interface.list(params)
46
+ search_results = json_response["hits"] || json_response["results"] || []
47
+ top_result = search_results[0]
48
+ if options[:go]
49
+ if top_result
50
+ print cyan,"Loading top search result: #{format_morpheus_type(top_result['type'])} #{top_result['name'] || top_result['id']} (score: #{top_result['score']})",reset,"\n"
51
+ return go_to_search_result(top_result, options)
52
+ else
53
+ # print cyan,"No results found.",reset,"\n"
54
+ raise_command_error "No search results for phrase '#{options[:phrase]}'"
55
+ end
56
+ end
57
+ render_response(json_response, options, "hits") do
58
+ print_h1 "Morpheus Search", parse_list_subtitles(options), options
59
+ if search_results.empty?
60
+ print cyan,"No results found.",reset,"\n"
61
+ else
62
+ columns = {
63
+ "Type" => lambda {|it| format_morpheus_type(it['type']) },
64
+ "ID" => 'id',
65
+ # "UUID" => 'uuid',
66
+ "Name" => 'name',
67
+ "Decription" => 'description',
68
+ #"Score" => 'score',
69
+ "Date Created" => lambda {|it| format_local_dt(it['dateCreated']) },
70
+ }
71
+ print as_pretty_table(search_results, columns.upcase_keys!, options)
72
+ print_results_pagination(json_response)
73
+ end
74
+ print reset,"\n"
75
+ end
76
+ if search_results.empty?
77
+ return 1, "no results found"
78
+ else
79
+ return 0, nil
80
+ end
81
+ end
82
+
83
+ protected
84
+
85
+ def format_morpheus_type(val)
86
+ if val == "ComputeSite"
87
+ "Group"
88
+ elsif val == "ComputeZone"
89
+ "Cloud"
90
+ elsif val == "ComputeServer"
91
+ "Host"
92
+ elsif val == "ComputeServerGroup"
93
+ "Cluster"
94
+ elsif val == "ComputeZonePool"
95
+ "Pool"
96
+ else
97
+ val
98
+ end
99
+ end
100
+
101
+ def go_to_search_result(result, options)
102
+ result_type = result['type']
103
+ cmd_class = lookup_morpheus_command(result['type'])
104
+ if cmd_class
105
+ get_options = []
106
+ get_options += (options[:remote] ? ["-r",options[:remote]] : [])
107
+ get_options += (options[:json] ? ["--json"] : [])
108
+ get_options += (options[:yaml] ? ["--yaml"] : [])
109
+ return cmd_class.new.handle(["get", result['id']] + get_options)
110
+ else
111
+ raise_command_error "Sorry, result cannot be loaded. type: #{result_type}, id: #{result['id']}, name: #{result['name']}"
112
+ end
113
+ end
114
+
115
+
116
+ def lookup_morpheus_command(result_type)
117
+ case result_type.to_s.downcase
118
+ when "computezone","cloud","zone"
119
+ Morpheus::Cli::Clouds
120
+ when "computesite","group"
121
+ Morpheus::Cli::Groups
122
+ when "computeserver","server","host"
123
+ Morpheus::Cli::Hosts
124
+ when "computeservergroup","serverGroup","cluster"
125
+ Morpheus::Cli::Clusters
126
+ when "instance"
127
+ Morpheus::Cli::Instances
128
+ when "app"
129
+ Morpheus::Cli::Apps
130
+ when "container"
131
+ Morpheus::Cli::Containers
132
+ when "computezonefolder","zonefolder","resourcefolder"
133
+ # crap, need result.zoneId, or resource-folders should not require cloud actually, get by id
134
+ # Morpheus::Cli::CloudFoldersCommand
135
+ nil
136
+ when "computezonepool","zonepool","resourcepool"
137
+ # crap, need result.zoneId, or resource-pools should not require cloud actually, get by id
138
+ # Morpheus::Cli::CloudResourcePoolsCommand
139
+ nil
140
+ # when "chassis"
141
+ # Morpheus::Cli::ChassisCommand
142
+ when "network"
143
+ Morpheus::Cli::NetworksCommand
144
+ when "networkgroup"
145
+ Morpheus::Cli::NetworkGroupsCommand
146
+ when "networkpool"
147
+ Morpheus::Cli::NetworkPoolsCommand
148
+ when "networkdomain"
149
+ Morpheus::Cli::NetworkDomainsCommand
150
+ when "virtualimage"
151
+ Morpheus::Cli::VirtualImages
152
+ when "loadbalancer"
153
+ Morpheus::Cli::LoadBalancers
154
+ # when "virtualserver","loadbalancerinstance"
155
+ # Morpheus::Cli::LoadBalancerInstances
156
+ when "instancetype"
157
+ # Morpheus::Cli::LibraryInstanceTypesCommand
158
+ Morpheus::Cli::LibraryInstanceTypes
159
+ when "instancetypelayout","layout"
160
+ Morpheus::Cli::LibraryLayoutsCommand
161
+ when "certificate"
162
+ # todo: WHAT! didnt I write certs already!?
163
+ Morpheus::Cli::CertificatesCommand
164
+ when "keypair"
165
+ Morpheus::Cli::KeyPairs
166
+ when "integration"
167
+ Morpheus::Cli::IntegrationsCommand
168
+ when "account","tenant"
169
+ Morpheus::Cli::TenantsCommand
170
+ when "user"
171
+ Morpheus::Cli::Users
172
+ when "role"
173
+ Morpheus::Cli::Roles
174
+ when "wikipage","wiki"
175
+ Morpheus::Cli::WikiCommand
176
+ else
177
+ nil
178
+ end
179
+ end
180
+
181
+ end
182
+