morpheus-cli 5.0.0 → 5.0.1

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