morpheus-cli 2.10.0 → 2.10.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/bin/morpheus +27 -32
  3. data/lib/morpheus/api/accounts_interface.rb +36 -47
  4. data/lib/morpheus/api/api_client.rb +141 -110
  5. data/lib/morpheus/api/app_templates_interface.rb +56 -72
  6. data/lib/morpheus/api/apps_interface.rb +111 -132
  7. data/lib/morpheus/api/auth_interface.rb +30 -0
  8. data/lib/morpheus/api/clouds_interface.rb +71 -76
  9. data/lib/morpheus/api/custom_instance_types_interface.rb +21 -46
  10. data/lib/morpheus/api/dashboard_interface.rb +10 -17
  11. data/lib/morpheus/api/deploy_interface.rb +60 -72
  12. data/lib/morpheus/api/deployments_interface.rb +53 -71
  13. data/lib/morpheus/api/groups_interface.rb +55 -45
  14. data/lib/morpheus/api/instance_types_interface.rb +19 -23
  15. data/lib/morpheus/api/instances_interface.rb +179 -177
  16. data/lib/morpheus/api/key_pairs_interface.rb +11 -17
  17. data/lib/morpheus/api/license_interface.rb +18 -23
  18. data/lib/morpheus/api/load_balancers_interface.rb +54 -69
  19. data/lib/morpheus/api/logs_interface.rb +25 -29
  20. data/lib/morpheus/api/options_interface.rb +13 -17
  21. data/lib/morpheus/api/provision_types_interface.rb +19 -22
  22. data/lib/morpheus/api/roles_interface.rb +75 -94
  23. data/lib/morpheus/api/security_group_rules_interface.rb +28 -37
  24. data/lib/morpheus/api/security_groups_interface.rb +39 -51
  25. data/lib/morpheus/api/servers_interface.rb +113 -115
  26. data/lib/morpheus/api/setup_interface.rb +31 -0
  27. data/lib/morpheus/api/task_sets_interface.rb +36 -38
  28. data/lib/morpheus/api/tasks_interface.rb +56 -69
  29. data/lib/morpheus/api/users_interface.rb +67 -76
  30. data/lib/morpheus/api/virtual_images_interface.rb +61 -61
  31. data/lib/morpheus/api/whoami_interface.rb +12 -15
  32. data/lib/morpheus/cli.rb +71 -60
  33. data/lib/morpheus/cli/accounts.rb +254 -315
  34. data/lib/morpheus/cli/alias_command.rb +219 -0
  35. data/lib/morpheus/cli/app_templates.rb +264 -272
  36. data/lib/morpheus/cli/apps.rb +608 -671
  37. data/lib/morpheus/cli/cli_command.rb +259 -21
  38. data/lib/morpheus/cli/cli_registry.rb +99 -14
  39. data/lib/morpheus/cli/clouds.rb +599 -372
  40. data/lib/morpheus/cli/config_file.rb +126 -0
  41. data/lib/morpheus/cli/credentials.rb +141 -117
  42. data/lib/morpheus/cli/dashboard_command.rb +48 -56
  43. data/lib/morpheus/cli/deployments.rb +254 -268
  44. data/lib/morpheus/cli/deploys.rb +150 -142
  45. data/lib/morpheus/cli/error_handler.rb +38 -0
  46. data/lib/morpheus/cli/groups.rb +551 -179
  47. data/lib/morpheus/cli/hosts.rb +862 -617
  48. data/lib/morpheus/cli/instance_types.rb +103 -95
  49. data/lib/morpheus/cli/instances.rb +1335 -1009
  50. data/lib/morpheus/cli/key_pairs.rb +82 -90
  51. data/lib/morpheus/cli/library.rb +498 -499
  52. data/lib/morpheus/cli/license.rb +83 -101
  53. data/lib/morpheus/cli/load_balancers.rb +314 -300
  54. data/lib/morpheus/cli/login.rb +66 -44
  55. data/lib/morpheus/cli/logout.rb +47 -46
  56. data/lib/morpheus/cli/mixins/accounts_helper.rb +69 -31
  57. data/lib/morpheus/cli/mixins/infrastructure_helper.rb +106 -0
  58. data/lib/morpheus/cli/mixins/print_helper.rb +181 -17
  59. data/lib/morpheus/cli/mixins/provisioning_helper.rb +535 -458
  60. data/lib/morpheus/cli/mixins/whoami_helper.rb +2 -2
  61. data/lib/morpheus/cli/option_parser.rb +35 -0
  62. data/lib/morpheus/cli/option_types.rb +232 -192
  63. data/lib/morpheus/cli/recent_activity_command.rb +61 -65
  64. data/lib/morpheus/cli/remote.rb +446 -199
  65. data/lib/morpheus/cli/roles.rb +884 -906
  66. data/lib/morpheus/cli/security_group_rules.rb +213 -203
  67. data/lib/morpheus/cli/security_groups.rb +237 -192
  68. data/lib/morpheus/cli/shell.rb +338 -231
  69. data/lib/morpheus/cli/tasks.rb +326 -308
  70. data/lib/morpheus/cli/users.rb +457 -462
  71. data/lib/morpheus/cli/version.rb +1 -1
  72. data/lib/morpheus/cli/version_command.rb +16 -18
  73. data/lib/morpheus/cli/virtual_images.rb +526 -345
  74. data/lib/morpheus/cli/whoami.rb +125 -111
  75. data/lib/morpheus/cli/workflows.rb +338 -185
  76. data/lib/morpheus/formatters.rb +8 -1
  77. data/lib/morpheus/logging.rb +1 -1
  78. data/lib/morpheus/rest_client.rb +17 -8
  79. metadata +9 -3
  80. data/lib/morpheus/api/custom_instance_types.rb +0 -55
@@ -10,622 +10,867 @@ require 'json'
10
10
  class Morpheus::Cli::Hosts
11
11
  include Morpheus::Cli::CliCommand
12
12
  include Morpheus::Cli::ProvisioningHelper
13
-
14
- def initialize()
15
- @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
16
- end
17
-
18
- def connect(opts)
19
- if opts[:remote]
20
- @appliance_url = opts[:remote]
21
- @appliance_name = opts[:remote]
22
- @access_token = Morpheus::Cli::Credentials.new(@appliance_name,@appliance_url).request_credentials(opts)
23
- else
24
- @access_token = Morpheus::Cli::Credentials.new(@appliance_name,@appliance_url).request_credentials(opts)
25
- end
26
- @api_client = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url)
27
- @clouds_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).clouds
28
- @options_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).options
29
- @active_groups = ::Morpheus::Cli::Groups.load_group_file
30
- @tasks_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).tasks
31
- @task_sets_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).task_sets
32
- @servers_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).servers
33
- @logs_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).logs
34
- if @access_token.empty?
35
- print_red_alert "Invalid Credentials. Unable to acquire access token. Please verify your credentials and try again."
36
- exit 1
37
- end
38
- end
39
-
40
- def handle(args)
41
- usage = "Usage: morpheus hosts [list,add,remove,logs,start,stop,run-workflow,make-managed,upgrade-agent,server-types] [name]"
42
- if args.empty?
43
- puts "\n#{usage}\n\n"
44
- exit 127
45
- end
46
-
47
- case args[0]
48
- when 'list'
49
- list(args[1..-1])
50
- when 'add'
51
- add(args[1..-1])
52
- when 'remove'
53
- remove(args[1..-1])
54
- when 'start'
55
- start(args[1..-1])
56
- when 'stop'
57
- start(args[1..-1])
58
- when 'run-workflow'
59
- run_workflow(args[1..-1])
60
- when 'upgrade-agent'
61
- upgrade(args[1..-1])
62
- when 'logs'
63
- logs(args[1..-1])
64
- when 'server-types'
65
- server_types(args[1..-1])
66
- else
67
- puts "\n#{usage}\n\n"
68
- exit 127 #Command now foud exit code
69
- end
70
- end
71
-
72
- def logs(args)
73
- options = {}
74
- optparse = OptionParser.new do|opts|
75
- opts.banner = "Usage: morpheus hosts logs [name]"
76
- build_common_options(opts, options, [:list, :json, :remote])
77
- end
78
- if args.count < 1
79
- puts "\n#{optparse.banner}\n\n"
80
- exit 1
81
- end
82
- optparse.parse(args)
83
- connect(options)
84
- begin
85
- host = find_host_by_name_or_id(args[0])
86
- logs = @logs_interface.server_logs([host['id']], { max: options[:max] || 100, offset: options[:offset] || 0, query: options[:phrase]})
87
- if options[:json]
88
- print JSON.pretty_generate(logs)
89
- else
90
- logs['data'].reverse.each do |log_entry|
91
- log_level = ''
92
- case log_entry['level']
93
- when 'INFO'
94
- log_level = "#{blue}#{bold}INFO#{reset}"
95
- when 'DEBUG'
96
- log_level = "#{white}#{bold}DEBUG#{reset}"
97
- when 'WARN'
98
- log_level = "#{yellow}#{bold}WARN#{reset}"
99
- when 'ERROR'
100
- log_level = "#{red}#{bold}ERROR#{reset}"
101
- when 'FATAL'
102
- log_level = "#{red}#{bold}FATAL#{reset}"
103
- end
104
- puts "[#{log_entry['ts']}] #{log_level} - #{log_entry['message']}"
105
- end
106
- print reset,"\n"
107
- end
108
- rescue RestClient::Exception => e
109
- print_rest_exception(e, options)
110
- exit 1
111
- end
112
- end
113
-
114
- def server_types(args)
115
- options = {zone: args[0]}
116
- optparse = OptionParser.new do|opts|
117
- opts.banner = "Usage: morpheus hosts server-types CLOUD"
118
- build_common_options(opts, options, [:json, :remote])
119
- end
120
- optparse.parse(args)
121
- if args.count < 1
122
- puts "\n#{optparse.banner}\n\n"
123
- exit 1
124
- end
125
- connect(options)
126
- params = {}
127
-
128
- zone=nil
129
-
130
- if !options[:zone].nil?
131
- zone = find_zone_by_name(nil, options[:zone])
132
- end
133
-
134
- if zone.nil?
135
- print_red_alert "Cloud not found for a"
136
- exit 1
137
- else
138
- cloud_type = cloud_type_for_id(zone['zoneTypeId'])
139
- end
140
- cloud_server_types = cloud_type['serverTypes'].select{|b| b['creatable'] == true}
141
- if options[:json]
142
- print JSON.pretty_generate(cloud_server_types)
143
- print "\n"
144
- else
145
-
146
- print "\n" ,cyan, bold, "Morpheus Server Types\n","==================", reset, "\n\n"
147
- if cloud_server_types.nil? || cloud_server_types.empty?
148
- puts yellow,"No server types found for the selected cloud.",reset
149
- else
150
- cloud_server_types.each do |server_type|
151
- print cyan, "= #{server_type['code']} - #{server_type['name']}\n"
152
- end
153
- end
154
- print reset,"\n\n"
155
- end
156
- end
157
-
158
- def add(args)
159
- options = {}
160
- optparse = OptionParser.new do|opts|
161
- opts.banner = "Usage: morpheus hosts add"
162
- opts.on( '-g', '--group GROUP', "Group Name" ) do |val|
163
- options[:group_name] = val
164
- end
165
- opts.on( '-G', '--group-id ID', "Group Id" ) do |val|
166
- options[:group_id] = val
167
- end
168
- opts.on( '-c', '--cloud CLOUD', "Cloud Name" ) do |val|
169
- options[:cloud_name] = val
170
- end
171
- opts.on( '-t', '--type TYPE', "Server Type" ) do |val|
172
- options[:server_type_code] = val
173
- end
174
- build_common_options(opts, options, [:options, :json, :dry_run, :remote])
175
- end
176
- optparse.parse(args)
177
- connect(options)
178
-
179
- # support old format of `hosts add CLOUD NAME`
180
- if args[0] && args[0] !~ /\A\-/
181
- options[:cloud_name] = args[0]
182
- if args[1] && args[1] !~ /\A\-/
183
- options[:host_name] = args[1]
184
- end
185
- end
186
-
187
- # use active group by default
188
- if !options[:group_name] && !options[:group_id]
189
- options[:group_id] = @active_groups[@appliance_name.to_sym]
190
- end
191
-
192
- params = {}
193
-
194
- # Group
195
- group_id = nil
196
- group = find_group_from_options(options)
197
- if group
198
- group_id = group["id"]
199
- else
200
- # print_red_alert "Group not found or specified!"
201
- # exit 1
202
- group_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'type' => 'select', 'fieldLabel' => 'Group', 'selectOptions' => get_available_groups(), 'required' => true, 'description' => 'Select Group.'}],options[:options],@api_client,{})
203
- group_id = cloud_prompt['group']
204
- end
205
-
206
- # Cloud
207
- cloud_id = nil
208
- cloud = find_cloud_from_options(group_id, options)
209
- if cloud
210
- cloud_id = cloud["id"]
211
- else
212
- # print_red_alert "Cloud not specified!"
213
- # exit 1
214
- cloud_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'type' => 'select', 'fieldLabel' => 'Cloud', 'selectOptions' => get_available_clouds(group_id), 'required' => true, 'description' => 'Select Cloud.'}],options[:options],@api_client,{groupId: group_id})
215
- cloud_id = cloud_prompt['cloud']
216
- cloud = find_cloud_by_id(group_id, cloud_id)
217
- end
218
-
219
- # Zone Type
220
- cloud_type = cloud_type_for_id(cloud['zoneTypeId'])
221
-
222
- # Server Type
223
- cloud_server_types = cloud_type['serverTypes'].select{|b| b['creatable'] == true }.sort { |x,y| x['displayOrder'] <=> y['displayOrder'] }
224
- if options[:server_type_code]
225
- server_type_code = options[:server_type_code]
226
- else
227
- server_type_options = cloud_server_types.collect {|it| {'name' => it['name'], 'value' => it['code']} }
228
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'type' => 'select', 'fieldLabel' => "Server Type", 'selectOptions' => server_type_options, 'required' => true, 'skipSingleOption' => true, 'description' => 'Choose a server type.'}], options[:options])
229
- server_type_code = v_prompt['type']
230
- end
231
- server_type = cloud_server_types.find {|it| it['code'] == server_type_code }
232
- if server_type.nil?
233
- print_red_alert "Server Type #{server_type_code} not found cloud #{cloud['name']}"
234
- exit 1
235
- end
236
-
237
- # Server Name
238
- host_name = nil
239
- if options[:host_name]
240
- host_name = options[:host_name]
241
- else
242
- name_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Server Name', 'type' => 'text', 'required' => true}], options[:options])
243
- host_name = name_prompt['name'] || ''
244
- end
245
-
246
- payload = {}
247
-
248
- # prompt for service plan
249
- service_plans_json = @servers_interface.service_plans({zoneId: cloud['id'], serverTypeId: server_type["id"]})
250
- service_plans = service_plans_json["plans"]
251
- service_plans_dropdown = service_plans.collect {|sp| {'name' => sp["name"], 'value' => sp["id"]} } # already sorted
252
- plan_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'servicePlan', 'type' => 'select', 'fieldLabel' => 'Plan', 'selectOptions' => service_plans_dropdown, 'required' => true, 'description' => 'Choose the appropriately sized plan for this server'}],options[:options])
253
- service_plan = service_plans.find {|sp| sp["id"] == plan_prompt['servicePlan'].to_i }
254
- # payload[:servicePlan] = plan_prompt['servicePlan']
255
-
256
- # prompt for volumes
257
- volumes = prompt_volumes(service_plan, options, @api_client, {})
258
- if !volumes.empty?
259
- payload[:volumes] = volumes
260
- end
261
-
262
- # prompt for network interfaces (if supported)
263
- if server_type["provisionType"] && server_type["provisionType"]["id"] && server_type["provisionType"]["hasNetworks"]
264
- begin
265
- network_interfaces = prompt_network_interfaces(cloud['id'], server_type["provisionType"]["id"], options, @api_client)
266
- if !network_interfaces.empty?
267
- payload[:networkInterfaces] = network_interfaces
268
- end
269
- rescue RestClient::Exception => e
270
- print_yellow_warning "Unable to load network options. Proceeding..."
271
- print_rest_exception(e, options) if Morpheus::Logging.print_stacktrace?
272
- end
273
- end
274
-
275
- server_type_option_types = server_type['optionTypes']
276
- # remove volume options if volumes were configured
277
- if !payload[:volumes].empty?
278
- server_type_option_types = reject_volume_option_types(server_type_option_types)
279
- end
280
- # remove networkId option if networks were configured above
281
- if !payload[:networkInterfaces].empty?
282
- server_type_option_types = reject_networking_option_types(server_type_option_types)
283
- end
284
- # remove cpu and memory option types, which now come from the plan
285
- server_type_option_types = reject_service_plan_option_types(server_type_option_types)
286
-
287
- params = Morpheus::Cli::OptionTypes.prompt(server_type_option_types,options[:options],@api_client, {zoneId: cloud['id']})
288
- begin
289
- params['server'] = params['server'] || {}
290
- payload = payload.merge({
291
- server: {
292
- name: host_name,
293
- zone: {id: cloud['id']},
294
- computeServerType: {id: server_type['id']},
295
- plan: {id: service_plan["id"]}
296
- }.merge(params['server'])
297
- })
298
- payload[:network] = params['network'] if params['network']
299
- payload[:config] = params['config'] if params['config']
300
-
301
- if options[:dry_run]
302
- print_dry_run("POST #{@appliance_url}/api/servers", payload)
303
- return
304
- end
305
- json_response = @servers_interface.create(payload)
306
- if options[:json]
307
- print JSON.pretty_generate(json_response)
308
- print "\n"
309
- else
310
- print_green_success "Provisioning Server..."
311
- list([])
312
- end
313
- rescue RestClient::Exception => e
314
- print_rest_exception(e, options)
315
- exit 1
316
- end
317
- end
318
-
319
- def remove(args)
320
- options = {}
321
- query_params = {removeResources: 'on', force: 'off'}
322
- optparse = OptionParser.new do|opts|
323
- opts.banner = "Usage: morpheus hosts remove [name] [-f] [-S]"
324
- opts.on( '-f', '--force', "Force Remove" ) do
325
- query_params[:force] = 'on'
326
- end
327
- opts.on( '-S', '--skip-remove-infrastructure', "Skip removal of underlying cloud infrastructure" ) do
328
- query_params[:removeResources] = 'off'
329
- end
330
-
331
- build_common_options(opts, options, [:auto_confirm, :json, :remote])
332
- end
333
- optparse.parse(args)
334
- if args.count < 1
335
- puts "\n#{optparse.banner}\n\n"
336
- exit 1
337
- end
338
- connect(options)
339
-
340
-
341
- begin
342
-
343
- server = find_host_by_name_or_id(args[0])
344
-
345
- unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the server '#{server['name']}'?", options)
346
- exit 1
347
- end
348
-
349
- json_response = @servers_interface.destroy(server['id'], query_params)
350
- if options[:json]
351
- print JSON.pretty_generate(json_response)
352
- print "\n"
353
- else
354
- print_green_success "Removing Server..."
355
- list([])
356
- end
357
- rescue RestClient::Exception => e
358
- print_rest_exception(e, options)
359
- exit 1
360
- end
361
- end
362
-
363
- def list(args)
364
- options = {}
365
- params = {}
366
- optparse = OptionParser.new do|opts|
367
- opts.banner = "Usage: morpheus hosts list"
368
- opts.on( '-g', '--group GROUP', "Group Name" ) do |val|
369
- options[:group_name] = val
370
- end
371
-
372
- build_common_options(opts, options, [:list, :json, :remote])
373
- end
374
- optparse.parse(args)
375
- connect(options)
376
- begin
377
-
378
- group = find_group_from_options(options)
379
- if group
380
- params['site'] = group['id']
381
- end
382
-
383
- [:phrase, :offset, :max, :sort, :direction].each do |k|
384
- params[k] = options[k] unless options[k].nil?
385
- end
386
-
387
- json_response = @servers_interface.get(params)
388
- servers = json_response['servers']
389
- if options[:json]
390
- print JSON.pretty_generate(json_response)
391
- print "\n"
392
- else
393
- print "\n" ,cyan, bold, "Morpheus Hosts\n","==================", reset, "\n\n"
394
- if servers.empty?
395
- puts yellow,"No hosts currently configured.",reset
396
- else
397
-
398
-
399
- server_table =servers.collect do |server|
400
- power_state = nil
401
- if server['powerState'] == 'on'
402
- power_state = "#{green}ON#{cyan}"
403
- elsif server['powerState'] == 'off'
404
- power_state = "#{red}OFF#{cyan}"
405
- else
406
- power_state = "#{white}#{server['powerState'].upcase}#{cyan}"
407
- end
408
- {id: server['id'], name: server['name'], platform: server['serverOs'] ? server['serverOs']['name'].upcase : 'N/A', type: server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged', status: server['status'], power: power_state}
409
- # print cyan, "= [#{server['id']}] #{server['name']} - #{server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged'} (#{server['status']}) Power: ", power_state, "\n"
410
- end
411
- end
412
- print cyan
413
- tp server_table, :id, :name, :type, :platform, :status, :power
414
- print reset,"\n\n"
415
- end
416
- rescue RestClient::Exception => e
417
- print_rest_exception(e, options)
418
- exit 1
419
- end
420
- end
421
-
422
-
423
- def start(args)
424
- options = {}
425
- optparse = OptionParser.new do|opts|
426
- opts.banner = "Usage: morpheus hosts start [name]"
427
- build_common_options(opts, options, [:json, :remote])
428
- end
429
- if args.count < 1
430
- puts "\n#{optparse.banner}\n\n"
431
- exit 1
432
- end
433
- optparse.parse(args)
434
- connect(options)
435
- begin
436
- host = find_host_by_name_or_id(args[0])
437
- json_response = @servers_interface.start(host['id'])
438
- if options[:json]
439
- print JSON.pretty_generate(json_response)
440
- print "\n"
441
- else
442
- puts "Host #{host[name]} started."
443
- end
444
- return
445
- rescue RestClient::Exception => e
446
- print_rest_exception(e, options)
447
- exit 1
448
- end
449
- end
450
-
451
- def stop(args)
452
- options = {}
453
- optparse = OptionParser.new do|opts|
454
- opts.banner = "Usage: morpheus hosts stop [name]"
455
- build_common_options(opts, options, [:json, :remote])
456
- end
457
- if args.count < 1
458
- puts "\n#{optparse.banner}\n\n"
459
- exit 1
460
- end
461
- optparse.parse(args)
462
- connect(options)
463
- begin
464
- host = find_host_by_name_or_id(args[0])
465
- json_response = @servers_interface.stop(host['id'])
466
- if options[:json]
467
- print JSON.pretty_generate(json_response)
468
- print "\n"
469
- else
470
- puts "Host #{host[name]} stopped."
471
- end
472
- return
473
- rescue RestClient::Exception => e
474
- print_rest_exception(e, options)
475
- exit 1
476
- end
477
- end
478
-
479
- def upgrade(args)
480
- options = {}
481
- optparse = OptionParser.new do|opts|
482
- opts.banner = "Usage: morpheus hosts upgrade [name]"
483
- build_common_options(opts, options, [:json, :remote])
484
- end
485
- if args.count < 1
486
- puts "\n#{optparse.banner}\n\n"
487
- exit 1
488
- end
489
- optparse.parse(args)
490
- connect(options)
491
- begin
492
- host = find_host_by_name_or_id(args[0])
493
- json_response = @servers_interface.upgrade(host['id'])
494
- if options[:json]
495
- print JSON.pretty_generate(json_response)
496
- print "\n"
497
- else
498
- puts "Host #{host[name]} upgrading..."
499
- end
500
- return
501
- rescue RestClient::Exception => e
502
- print_rest_exception(e, options)
503
- exit 1
504
- end
505
- end
506
-
507
- def run_workflow(args)
508
- options = {}
509
-
510
- optparse = OptionParser.new do|opts|
511
- opts.banner = "Usage: morpheus hosts run-workflow [HOST] [name]"
512
- build_common_options(opts, options, [:json, :remote])
513
- end
514
- if args.count < 2
515
- puts "\n#{optparse}\n\n"
516
- exit 1
517
- end
518
-
519
- optparse.parse(args)
520
- connect(options)
521
- host = find_host_by_name_or_id(args[0])
522
- workflow = find_workflow_by_name(args[1])
523
- task_types = @tasks_interface.task_types()
524
- editable_options = []
525
- workflow['taskSetTasks'].sort{|a,b| a['taskOrder'] <=> b['taskOrder']}.each do |task_set_task|
526
- task_type_id = task_set_task['task']['taskType']['id']
527
- task_type = task_types['taskTypes'].find{ |current_task_type| current_task_type['id'] == task_type_id}
528
- task_opts = task_type['optionTypes'].select { |otype| otype['editable']}
529
- if !task_opts.nil? && !task_opts.empty?
530
- editable_options += task_opts.collect do |task_opt|
531
- new_task_opt = task_opt.clone
532
- new_task_opt['fieldContext'] = "#{task_set_task['id']}.#{new_task_opt['fieldContext']}"
533
- end
534
- end
535
- end
536
- params = options[:options] || {}
537
-
538
- if params.empty? && !editable_options.empty?
539
- puts "\n#{optparse.banner}\n\n"
540
- option_lines = editable_options.collect {|it| "\t-O #{it['fieldContext'] ? (it['fieldContext'] + '.') : ''}#{it['fieldName']}=\"value\"" }.join("\n")
541
- puts "\nAvailable Options:\n#{option_lines}\n\n"
542
- exit 1
543
- end
544
-
545
- workflow_payload = {taskSet: {"#{workflow['id']}" => params }}
546
- begin
547
-
548
- json_response = @servers_interface.workflow(host['id'],workflow['id'], workflow_payload)
549
- if options[:json]
550
- print JSON.pretty_generate(json_response)
551
- print "\n"
552
- else
553
- puts "Running workflow..."
554
- end
555
- rescue RestClient::Exception => e
556
- print_rest_exception(e, options)
557
- exit 1
558
- end
559
- end
560
-
561
- private
562
-
563
- def find_host_by_id(id)
564
- results = @servers_interface.get(id.to_i)
565
- if results['server'].empty?
566
- print_red_alert "Server not found by id #{id}"
567
- exit 1
568
- end
569
- return results['server']
570
- end
571
-
572
- def find_host_by_name(name)
573
- results = @servers_interface.get({name: name})
574
- if results['servers'].empty?
575
- print_red_alert "Server not found by name #{name}"
576
- exit 1
577
- elsif results['servers'].size > 1
578
- print_red_alert "Multiple Servers exist with the name #{name}. Try using id instead"
579
- exit 1
580
- end
581
- return results['servers'][0]
582
- end
583
-
584
- def find_host_by_name_or_id(val)
585
- if val.to_s =~ /\A\d{1,}\Z/
586
- return find_host_by_id(val)
587
- else
588
- return find_host_by_name(val)
589
- end
590
- end
591
-
592
- def find_zone_by_name(group_id, name)
593
- zone_results = @clouds_interface.get({groupId: group_id, name: name})
594
- if zone_results['zones'].empty?
595
- print_red_alert "Cloud not found by name #{name}"
596
- exit 1
597
- end
598
- return zone_results['zones'][0]
599
- end
600
-
601
- def find_server_type(zone, name)
602
- server_type = zone['serverTypes'].select do |sv_type|
603
- (sv_type['name'].downcase == name.downcase || sv_type['code'].downcase == name.downcase) && sv_type['creatable'] == true
604
- end
605
- if server_type.nil?
606
- print_red_alert "Server Type Not Selectable"
607
- end
608
- return server_type
609
- end
610
-
611
- def cloud_type_for_id(id)
612
- cloud_types = @clouds_interface.cloud_types['zoneTypes']
613
- cloud_type = cloud_types.find { |z| z['id'].to_i == id.to_i}
614
- if cloud_type.nil?
615
- print_red_alert "Cloud Type not found by id #{id}"
616
- exit 1
617
- end
618
- return cloud_type
619
- end
620
-
621
- def find_workflow_by_name(name)
622
- task_set_results = @task_sets_interface.get(name)
623
- if !task_set_results['taskSets'].nil? && !task_set_results['taskSets'].empty?
624
- return task_set_results['taskSets'][0]
625
- else
626
- print_red_alert "Workflow not found by name #{name}"
627
- exit 1
628
- end
629
- end
13
+ register_subcommands :list, :get, :stats, :add, :remove, :logs, :start, :stop, :resize, :run_workflow, :make_managed, :upgrade_agent, :server_types
14
+ alias_subcommand :details, :get
15
+ set_default_subcommand :list
16
+
17
+ def initialize()
18
+ # @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
19
+ end
20
+
21
+ def connect(opts)
22
+ @api_client = establish_remote_appliance_connection(opts)
23
+ @clouds_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).clouds
24
+ @options_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).options
25
+ @tasks_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).tasks
26
+ @task_sets_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).task_sets
27
+ @servers_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).servers
28
+ @logs_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).logs
29
+ @active_group_id = Morpheus::Cli::Groups.active_group
30
+ end
31
+
32
+ def handle(args)
33
+ handle_subcommand(args)
34
+ end
35
+
36
+ def list(args)
37
+ options = {}
38
+ params = {}
39
+ optparse = OptionParser.new do|opts|
40
+ opts.banner = subcommand_usage()
41
+ opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
42
+ options[:group] = val
43
+ end
44
+ opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
45
+ options[:cloud] = val
46
+ end
47
+ build_common_options(opts, options, [:list, :json, :dry_run, :remote])
48
+ end
49
+ optparse.parse!(args)
50
+ connect(options)
51
+ begin
52
+ group = options[:group] ? find_group_by_name_or_id_for_provisioning(options[:group]) : nil
53
+ if group
54
+ params['siteId'] = group['id']
55
+ end
56
+
57
+ # argh, this doesn't work because group_id is required for options/clouds
58
+ # cloud = options[:cloud] ? find_cloud_by_name_or_id_for_provisioning(group_id, options[:cloud]) : nil
59
+ cloud = options[:cloud] ? find_zone_by_name_or_id(nil, options[:cloud]) : nil
60
+ if cloud
61
+ params['zoneId'] = cloud['id']
62
+ end
63
+
64
+ [:phrase, :offset, :max, :sort, :direction].each do |k|
65
+ params[k] = options[k] unless options[k].nil?
66
+ end
67
+
68
+ if options[:dry_run]
69
+ print_dry_run @servers_interface.dry.get(params)
70
+ return
71
+ end
72
+ json_response = @servers_interface.get(params)
73
+
74
+ if options[:json]
75
+ print JSON.pretty_generate(json_response)
76
+ print "\n"
77
+ else
78
+ servers = json_response['servers']
79
+ title = "Morpheus Hosts"
80
+ subtitles = []
81
+ if group
82
+ subtitles << "Group: #{group['name']}".strip
83
+ end
84
+ if cloud
85
+ subtitles << "Cloud: #{cloud['name']}".strip
86
+ end
87
+ if params[:phrase]
88
+ subtitles << "Search: #{params[:phrase]}".strip
89
+ end
90
+ subtitle = subtitles.join(', ')
91
+ print "\n" ,cyan, bold, title, (subtitle.empty? ? "" : " - #{subtitle}"), "\n", "==================", reset, "\n\n"
92
+ if servers.empty?
93
+ puts yellow,"No hosts found.",reset
94
+ else
95
+ print_servers_table(servers)
96
+ print_results_pagination(json_response)
97
+ end
98
+ print reset,"\n"
99
+ end
100
+ rescue RestClient::Exception => e
101
+ print_rest_exception(e, options)
102
+ exit 1
103
+ end
104
+ end
105
+
106
+ def get(args)
107
+ options = {}
108
+ optparse = OptionParser.new do|opts|
109
+ opts.banner = subcommand_usage("[name]")
110
+ build_common_options(opts, options, [:json, :dry_run, :remote])
111
+ end
112
+ optparse.parse!(args)
113
+ if args.count < 1
114
+ puts optparse
115
+ exit 1
116
+ end
117
+ connect(options)
118
+ begin
119
+ if options[:dry_run]
120
+ if args[0].to_s =~ /\A\d{1,}\Z/
121
+ print_dry_run @servers_interface.dry.get(args[0].to_i)
122
+ else
123
+ print_dry_run @servers_interface.dry.get({name: args[0]})
124
+ end
125
+ return
126
+ end
127
+ server = find_host_by_name_or_id(args[0])
128
+ json_response = @servers_interface.get(server['id'])
129
+ if options[:json]
130
+ print JSON.pretty_generate(json_response), "\n"
131
+ return
132
+ end
133
+ server = json_response['server']
134
+ #stats = server['stats'] || json_response['stats'] || {}
135
+ stats = json_response['stats'] || {}
136
+
137
+ print "\n" ,cyan, bold, "Host Details\n","==================", reset, "\n\n"
138
+ print cyan
139
+ puts "ID: #{server['id']}"
140
+ puts "Name: #{server['name']}"
141
+ puts "Description: #{server['description']}"
142
+ puts "Account: #{server['account'] ? server['account']['name'] : ''}"
143
+ #puts "Group: #{server['group'] ? server['group']['name'] : ''}"
144
+ #puts "Cloud: #{server['cloud'] ? server['cloud']['name'] : ''}"
145
+ puts "Cloud: #{server['zone'] ? server['zone']['name'] : ''}"
146
+ puts "Nodes: #{server['containers'] ? server['containers'].size : ''}"
147
+ puts "Type: #{server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged'}"
148
+ puts "Platform: #{server['serverOs'] ? server['serverOs']['name'].upcase : 'N/A'}"
149
+ puts "Plan: #{server['plan'] ? server['plan']['name'] : ''}"
150
+ puts "Status: #{format_host_status(server)}"
151
+ puts "Power: #{format_server_power_state(server)}"
152
+ if ((stats['maxMemory'].to_i != 0) || (stats['maxStorage'].to_i != 0))
153
+ # stats_map = {}
154
+ print "\n"
155
+ #print "\n" ,cyan, bold, "Host Stats\n","==================", reset, "\n\n"
156
+ # stats_map[:memory] = "#{Filesize.from("#{stats['usedMemory']} B").pretty} / #{Filesize.from("#{stats['maxMemory']} B").pretty}"
157
+ # stats_map[:storage] = "#{Filesize.from("#{stats['usedStorage']} B").pretty} / #{Filesize.from("#{stats['maxStorage']} B").pretty}"
158
+ # stats_map[:cpu] = "#{stats['cpuUsage'].to_f.round(2)}%"
159
+ # tp [stats_map], :memory,:storage,:cpu
160
+ print_stats_usage(stats)
161
+ else
162
+ #print yellow, "No stat data.", reset
163
+ end
164
+
165
+ print reset, "\n"
166
+
167
+ rescue RestClient::Exception => e
168
+ print_rest_exception(e, options)
169
+ exit 1
170
+ end
171
+ end
172
+
173
+ def stats(args)
174
+ options = {}
175
+ optparse = OptionParser.new do|opts|
176
+ opts.banner = subcommand_usage("[name]")
177
+ build_common_options(opts, options, [:json, :dry_run, :remote])
178
+ end
179
+ optparse.parse!(args)
180
+ if args.count < 1
181
+ puts optparse
182
+ exit 1
183
+ end
184
+ connect(options)
185
+ begin
186
+ if options[:dry_run]
187
+ if args[0].to_s =~ /\A\d{1,}\Z/
188
+ print_dry_run @servers_interface.dry.get(args[0].to_i)
189
+ else
190
+ print_dry_run @servers_interface.dry.get({name: args[0]})
191
+ end
192
+ return
193
+ end
194
+ server = find_host_by_name_or_id(args[0])
195
+ json_response = @servers_interface.get(server['id'])
196
+ if options[:json]
197
+ print JSON.pretty_generate(json_response), "\n"
198
+ return
199
+ end
200
+ server = json_response['server']
201
+ #stats = server['stats'] || json_response['stats'] || {}
202
+ stats = json_response['stats'] || {}
203
+
204
+ print "\n" ,cyan, bold, "Host Stats: #{server['name']} (#{server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged'})\n","==================", "\n\n", reset, cyan
205
+ puts "Status: #{format_host_status(server)}"
206
+ puts "Power: #{format_server_power_state(server)}"
207
+ if ((stats['maxMemory'].to_i != 0) || (stats['maxStorage'].to_i != 0))
208
+ # stats_map = {}
209
+ print "\n"
210
+ #print "\n" ,cyan, bold, "Host Stats\n","==================", reset, "\n\n"
211
+ # stats_map[:memory] = "#{Filesize.from("#{stats['usedMemory']} B").pretty} / #{Filesize.from("#{stats['maxMemory']} B").pretty}"
212
+ # stats_map[:storage] = "#{Filesize.from("#{stats['usedStorage']} B").pretty} / #{Filesize.from("#{stats['maxStorage']} B").pretty}"
213
+ # stats_map[:cpu] = "#{stats['cpuUsage'].to_f.round(2)}%"
214
+ # tp [stats_map], :memory,:storage,:cpu
215
+ print_stats_usage(stats)
216
+ else
217
+ #print yellow, "No stat data.", reset
218
+ end
219
+
220
+ print reset, "\n"
221
+
222
+ rescue RestClient::Exception => e
223
+ print_rest_exception(e, options)
224
+ exit 1
225
+ end
226
+ end
227
+
228
+ def logs(args)
229
+ options = {}
230
+ optparse = OptionParser.new do|opts|
231
+ opts.banner = subcommand_usage("[name]")
232
+ build_common_options(opts, options, [:list, :json, :dry_run, :remote])
233
+ end
234
+ optparse.parse!(args)
235
+ if args.count < 1
236
+ puts optparse
237
+ exit 1
238
+ end
239
+ connect(options)
240
+ begin
241
+ host = find_host_by_name_or_id(args[0])
242
+ params = {}
243
+ [:phrase, :offset, :max, :sort, :direction].each do |k|
244
+ params[k] = options[k] unless options[k].nil?
245
+ end
246
+ params[:query] = params.delete(:phrase) unless params[:phrase].nil?
247
+ if options[:dry_run]
248
+ print_dry_run @logs_interface.dry.server_logs([host['id']], params)
249
+ return
250
+ end
251
+ logs = @logs_interface.server_logs([host['id']], params)
252
+ output = ""
253
+ if options[:json]
254
+ output << JSON.pretty_generate(logs)
255
+ else
256
+ logs['data'].reverse.each do |log_entry|
257
+ log_level = ''
258
+ case log_entry['level']
259
+ when 'INFO'
260
+ log_level = "#{blue}#{bold}INFO#{reset}"
261
+ when 'DEBUG'
262
+ log_level = "#{white}#{bold}DEBUG#{reset}"
263
+ when 'WARN'
264
+ log_level = "#{yellow}#{bold}WARN#{reset}"
265
+ when 'ERROR'
266
+ log_level = "#{red}#{bold}ERROR#{reset}"
267
+ when 'FATAL'
268
+ log_level = "#{red}#{bold}FATAL#{reset}"
269
+ end
270
+ output << "[#{log_entry['ts']}] #{log_level} - #{log_entry['message']}\n"
271
+ end
272
+ end
273
+ print output, reset, "\n"
274
+ rescue RestClient::Exception => e
275
+ print_rest_exception(e, options)
276
+ exit 1
277
+ end
278
+ end
279
+
280
+ def server_types(args)
281
+ options = {}
282
+ optparse = OptionParser.new do|opts|
283
+ opts.banner = subcommand_usage("[cloud]")
284
+ build_common_options(opts, options, [:json, :remote])
285
+ end
286
+ optparse.parse!(args)
287
+ if args.count < 1
288
+ puts optparse
289
+ exit 1
290
+ end
291
+ options[:zone] = args[0]
292
+ connect(options)
293
+ params = {}
294
+
295
+ zone = find_zone_by_name_or_id(nil, options[:zone])
296
+ cloud_type = cloud_type_for_id(zone['zoneTypeId'])
297
+ cloud_server_types = cloud_type['serverTypes'].select{|b| b['creatable'] == true}
298
+ cloud_server_types = cloud_server_types.sort { |x,y| x['displayOrder'] <=> y['displayOrder'] }
299
+ if options[:json]
300
+ print JSON.pretty_generate(cloud_server_types)
301
+ print "\n"
302
+ else
303
+ print "\n" ,cyan, bold, "Morpheus Server Types - Cloud: #{zone['name']}\n","==================", reset, "\n\n"
304
+ if cloud_server_types.nil? || cloud_server_types.empty?
305
+ puts yellow,"No server types found for the selected cloud.",reset
306
+ else
307
+ cloud_server_types.each do |server_type|
308
+ print cyan, "[#{server_type['code']}]".ljust(20), " - ", "#{server_type['name']}", "\n"
309
+ end
310
+ end
311
+ print reset,"\n"
312
+ end
313
+ end
314
+
315
+ def add(args)
316
+ options = {}
317
+ optparse = OptionParser.new do|opts|
318
+ opts.banner = subcommand_usage("[cloud]", "[name]")
319
+ opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
320
+ options[:group] = val
321
+ end
322
+ opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
323
+ options[:cloud] = val
324
+ end
325
+ opts.on( '-t', '--type TYPE', "Server Type Code" ) do |val|
326
+ options[:server_type_code] = val
327
+ end
328
+ build_common_options(opts, options, [:options, :json, :dry_run, :remote])
329
+ end
330
+ optparse.parse!(args)
331
+ connect(options)
332
+
333
+ # support old format of `hosts add CLOUD NAME`
334
+ if args[0]
335
+ options[:cloud] = args[0]
336
+ end
337
+ if args[1]
338
+ options[:host_name] = args[1]
339
+ end
340
+ # use active group by default
341
+ options[:group] ||= @active_group_id
342
+
343
+ params = {}
344
+
345
+ # Group
346
+ group_id = nil
347
+ group = options[:group] ? find_group_by_name_or_id_for_provisioning(options[:group]) : nil
348
+ if group
349
+ group_id = group["id"]
350
+ else
351
+ # print_red_alert "Group not found or specified!"
352
+ # exit 1
353
+ group_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'type' => 'select', 'fieldLabel' => 'Group', 'selectOptions' => get_available_groups(), 'required' => true, 'description' => 'Select Group.'}],options[:options],@api_client,{})
354
+ group_id = group_prompt['group']
355
+ end
356
+
357
+ # Cloud
358
+ cloud_id = nil
359
+ cloud = options[:cloud] ? find_cloud_by_name_or_id_for_provisioning(group_id, options[:cloud]) : nil
360
+ if cloud
361
+ cloud_id = cloud["id"]
362
+ else
363
+ available_clouds = get_available_clouds(group_id)
364
+ if available_clouds.empty?
365
+ print_red_alert "Group #{group['name']} has no available clouds"
366
+ exit 1
367
+ end
368
+ cloud_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'type' => 'select', 'fieldLabel' => 'Cloud', 'selectOptions' => available_clouds, 'required' => true, 'description' => 'Select Cloud.'}],options[:options],@api_client,{groupId: group_id})
369
+ cloud_id = cloud_prompt['cloud']
370
+ cloud = find_cloud_by_id_for_provisioning(group_id, cloud_id)
371
+ end
372
+
373
+ # Zone Type
374
+ cloud_type = cloud_type_for_id(cloud['zoneTypeId'])
375
+
376
+ # Server Type
377
+ cloud_server_types = cloud_type['serverTypes'].select{|b| b['creatable'] == true }.sort { |x,y| x['displayOrder'] <=> y['displayOrder'] }
378
+ if options[:server_type_code]
379
+ server_type_code = options[:server_type_code]
380
+ else
381
+ server_type_options = cloud_server_types.collect {|it| {'name' => it['name'], 'value' => it['code']} }
382
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'type' => 'select', 'fieldLabel' => "Server Type", 'selectOptions' => server_type_options, 'required' => true, 'skipSingleOption' => true, 'description' => 'Choose a server type.'}], options[:options])
383
+ server_type_code = v_prompt['type']
384
+ end
385
+ server_type = cloud_server_types.find {|it| it['code'] == server_type_code }
386
+ if server_type.nil?
387
+ print_red_alert "Server Type #{server_type_code} not found cloud #{cloud['name']}"
388
+ exit 1
389
+ end
390
+
391
+ # Server Name
392
+ host_name = nil
393
+ if options[:host_name]
394
+ host_name = options[:host_name]
395
+ else
396
+ name_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Server Name', 'type' => 'text', 'required' => true}], options[:options])
397
+ host_name = name_prompt['name'] || ''
398
+ end
399
+
400
+ payload = {}
401
+ # prompt for service plan
402
+ service_plans_json = @servers_interface.service_plans({zoneId: cloud['id'], serverTypeId: server_type["id"]})
403
+ service_plans = service_plans_json["plans"]
404
+ service_plans_dropdown = service_plans.collect {|sp| {'name' => sp["name"], 'value' => sp["id"]} } # already sorted
405
+ plan_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'plan', 'type' => 'select', 'fieldLabel' => 'Plan', 'selectOptions' => service_plans_dropdown, 'required' => true, 'description' => 'Choose the appropriately sized plan for this server'}],options[:options])
406
+ service_plan = service_plans.find {|sp| sp["id"] == plan_prompt['plan'].to_i }
407
+
408
+ # prompt for volumes
409
+ volumes = prompt_volumes(service_plan, options, @api_client, {})
410
+ if !volumes.empty?
411
+ payload[:volumes] = volumes
412
+ end
413
+
414
+ # prompt for network interfaces (if supported)
415
+ if server_type["provisionType"] && server_type["provisionType"]["id"] && server_type["provisionType"]["hasNetworks"]
416
+ begin
417
+ network_interfaces = prompt_network_interfaces(cloud['id'], server_type["provisionType"]["id"], options)
418
+ if !network_interfaces.empty?
419
+ payload[:networkInterfaces] = network_interfaces
420
+ end
421
+ rescue RestClient::Exception => e
422
+ print_yellow_warning "Unable to load network options. Proceeding..."
423
+ print_rest_exception(e, options) if Morpheus::Logging.debug?
424
+ end
425
+ end
426
+
427
+ server_type_option_types = server_type['optionTypes']
428
+ # remove volume options if volumes were configured
429
+ if !payload[:volumes].empty?
430
+ server_type_option_types = reject_volume_option_types(server_type_option_types)
431
+ end
432
+ # remove networkId option if networks were configured above
433
+ if !payload[:networkInterfaces].empty?
434
+ server_type_option_types = reject_networking_option_types(server_type_option_types)
435
+ end
436
+ # remove cpu and memory option types, which now come from the plan
437
+ server_type_option_types = reject_service_plan_option_types(server_type_option_types)
438
+
439
+ params = Morpheus::Cli::OptionTypes.prompt(server_type_option_types,options[:options],@api_client, {zoneId: cloud['id']})
440
+ begin
441
+ params['server'] = params['server'] || {}
442
+ payload = payload.merge({
443
+ server: {
444
+ name: host_name,
445
+ zone: {id: cloud['id']},
446
+ computeServerType: {id: server_type['id']},
447
+ plan: {id: service_plan["id"]}
448
+ }.merge(params['server'])
449
+ })
450
+ payload[:network] = params['network'] if params['network']
451
+ payload[:config] = params['config'] if params['config']
452
+ if options[:dry_run]
453
+ print_dry_run @servers_interface.dry.create(payload)
454
+ return
455
+ end
456
+ json_response = @servers_interface.create(payload)
457
+ if options[:json]
458
+ print JSON.pretty_generate(json_response)
459
+ print "\n"
460
+ else
461
+ print_green_success "Provisioning Server..."
462
+ list([])
463
+ end
464
+ rescue RestClient::Exception => e
465
+ print_rest_exception(e, options)
466
+ exit 1
467
+ end
468
+ end
469
+
470
+ def remove(args)
471
+ options = {}
472
+ query_params = {removeResources: 'on', force: 'off'}
473
+ optparse = OptionParser.new do|opts|
474
+ opts.banner = subcommand_usage("[name] [-f] [-S]")
475
+ opts.on( '-f', '--force', "Force Remove" ) do
476
+ query_params[:force] = 'on'
477
+ end
478
+ opts.on( '-S', '--skip-remove-infrastructure', "Skip removal of underlying cloud infrastructure" ) do
479
+ query_params[:removeResources] = 'off'
480
+ end
481
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
482
+ end
483
+ optparse.parse!(args)
484
+ if args.count < 1
485
+ puts optparse
486
+ exit 1
487
+ end
488
+ connect(options)
489
+
490
+ begin
491
+ server = find_host_by_name_or_id(args[0])
492
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the server '#{server['name']}'?", options)
493
+ exit 1
494
+ end
495
+ if options[:dry_run]
496
+ print_dry_run @servers_interface.dry.destroy(server['id'], query_params)
497
+ return
498
+ end
499
+ json_response = @servers_interface.destroy(server['id'], query_params)
500
+ if options[:json]
501
+ print JSON.pretty_generate(json_response)
502
+ print "\n"
503
+ else
504
+ print_green_success "Removing Server..."
505
+ list([])
506
+ end
507
+ rescue RestClient::Exception => e
508
+ print_rest_exception(e, options)
509
+ exit 1
510
+ end
511
+ end
512
+
513
+ def start(args)
514
+ options = {}
515
+ optparse = OptionParser.new do|opts|
516
+ opts.banner = subcommand_usage("[name]")
517
+ build_common_options(opts, options, [:json, :remote])
518
+ end
519
+ optparse.parse!(args)
520
+ if args.count < 1
521
+ puts optparse
522
+ exit 1
523
+ end
524
+ connect(options)
525
+ begin
526
+ host = find_host_by_name_or_id(args[0])
527
+ json_response = @servers_interface.start(host['id'])
528
+ if options[:json]
529
+ print JSON.pretty_generate(json_response)
530
+ print "\n"
531
+ else
532
+ puts "Host #{host['name']} started."
533
+ end
534
+ return
535
+ rescue RestClient::Exception => e
536
+ print_rest_exception(e, options)
537
+ exit 1
538
+ end
539
+ end
540
+
541
+ def stop(args)
542
+ options = {}
543
+ optparse = OptionParser.new do|opts|
544
+ opts.banner = subcommand_usage("[name]")
545
+ build_common_options(opts, options, [:json, :remote])
546
+ end
547
+ optparse.parse!(args)
548
+ if args.count < 1
549
+ puts optparse
550
+ exit 1
551
+ end
552
+ connect(options)
553
+ begin
554
+ host = find_host_by_name_or_id(args[0])
555
+ if options[:dry_run]
556
+ print_dry_run @servers_interface.dry.stop(host['id'])
557
+ return
558
+ end
559
+ json_response = @servers_interface.stop(host['id'])
560
+ if options[:json]
561
+ print JSON.pretty_generate(json_response)
562
+ print "\n"
563
+ else
564
+ puts "Host #{host['name']} stopped." unless options[:quiet]
565
+ end
566
+ return
567
+ rescue RestClient::Exception => e
568
+ print_rest_exception(e, options)
569
+ exit 1
570
+ end
571
+ end
572
+
573
+ def resize(args)
574
+ options = {}
575
+ optparse = OptionParser.new do|opts|
576
+ opts.banner = subcommand_usage("[name]")
577
+ build_common_options(opts, options, [:options, :json, :dry_run, :quiet, :remote])
578
+ end
579
+ optparse.parse!(args)
580
+ if args.count < 1
581
+ puts optparse
582
+ exit 1
583
+ end
584
+ connect(options)
585
+ begin
586
+ server = find_host_by_name_or_id(args[0])
587
+
588
+ group_id = server["siteId"] || erver['group']['id']
589
+ cloud_id = server["zoneId"] || server["zone"]["id"]
590
+ server_type_id = server['computeServerType']['id']
591
+ plan_id = server['plan']['id']
592
+ payload = {
593
+ :server => {:id => server["id"]}
594
+ }
595
+
596
+ # avoid 500 error
597
+ # payload[:servicePlanOptions] = {}
598
+ unless options[:no_prompt]
599
+ puts "\nDue to limitations by most Guest Operating Systems, Disk sizes can only be expanded and not reduced.\nIf a smaller plan is selected, memory and CPU (if relevant) will be reduced but storage will not.\n\n"
600
+ # unless hot_resize
601
+ # puts "\nWARNING: Resize actions for this server will cause instances to be restarted.\n\n"
602
+ # end
603
+ end
604
+
605
+ # prompt for service plan
606
+ service_plans_json = @servers_interface.service_plans({zoneId: cloud_id, serverTypeId: server_type_id})
607
+ service_plans = service_plans_json["plans"]
608
+ service_plans_dropdown = service_plans.collect {|sp| {'name' => sp["name"], 'value' => sp["id"]} } # already sorted
609
+ service_plans_dropdown.each do |plan|
610
+ if plan['value'] && plan['value'].to_i == plan_id.to_i
611
+ plan['name'] = "#{plan['name']} (current)"
612
+ end
613
+ end
614
+ plan_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'plan', 'type' => 'select', 'fieldLabel' => 'Plan', 'selectOptions' => service_plans_dropdown, 'required' => true, 'description' => 'Choose the appropriately sized plan for this server'}],options[:options])
615
+ service_plan = service_plans.find {|sp| sp["id"] == plan_prompt['plan'].to_i }
616
+ payload[:server][:plan] = {id: service_plan["id"]}
617
+
618
+ # fetch volumes
619
+ volumes_response = @servers_interface.volumes(server['id'])
620
+ current_volumes = volumes_response['volumes'].sort {|x,y| x['displayOrder'] <=> y['displayOrder'] }
621
+
622
+ # prompt for volumes
623
+ volumes = prompt_resize_volumes(current_volumes, service_plan, options)
624
+ if !volumes.empty?
625
+ payload[:volumes] = volumes
626
+ end
627
+
628
+ # todo: reconfigure networks
629
+ # need to get provision_type_id for network info
630
+ # prompt for network interfaces (if supported)
631
+ # if server_type["provisionType"] && server_type["provisionType"]["id"] && server_type["provisionType"]["hasNetworks"]
632
+ # begin
633
+ # network_interfaces = prompt_network_interfaces(cloud['id'], server_type["provisionType"]["id"], options)
634
+ # if !network_interfaces.empty?
635
+ # payload[:networkInterfaces] = network_interfaces
636
+ # end
637
+ # rescue RestClient::Exception => e
638
+ # print_yellow_warning "Unable to load network options. Proceeding..."
639
+ # print_rest_exception(e, options) if Morpheus::Logging.debug?
640
+ # end
641
+ # end
642
+
643
+ # only amazon supports this option
644
+ # for now, always do this
645
+ payload[:deleteOriginalVolumes] = true
646
+
647
+ if options[:dry_run]
648
+ print_dry_run @servers_interface.dry.resize(server['id'], payload)
649
+ return
650
+ end
651
+ json_response = @servers_interface.resize(server['id'], payload)
652
+ if options[:json]
653
+ print JSON.pretty_generate(json_response)
654
+ print "\n"
655
+ else
656
+ unless options[:quiet]
657
+ print_green_success "Resizing server #{server['name']}"
658
+ list([])
659
+ end
660
+ end
661
+ rescue RestClient::Exception => e
662
+ print_rest_exception(e, options)
663
+ exit 1
664
+ end
665
+ end
666
+
667
+ def upgrade(args)
668
+ options = {}
669
+ optparse = OptionParser.new do|opts|
670
+ opts.banner = subcommand_usage("[name]")
671
+ build_common_options(opts, options, [:json, :quiet, :remote])
672
+ end
673
+ optparse.parse!(args)
674
+ if args.count < 1
675
+ puts optparse
676
+ exit 1
677
+ end
678
+ connect(options)
679
+ begin
680
+ host = find_host_by_name_or_id(args[0])
681
+ json_response = @servers_interface.upgrade(host['id'])
682
+ if options[:json]
683
+ print JSON.pretty_generate(json_response)
684
+ print "\n"
685
+ else
686
+ puts "Host #{host['name']} upgrading..." unless options[:quiet]
687
+ end
688
+ return
689
+ rescue RestClient::Exception => e
690
+ print_rest_exception(e, options)
691
+ exit 1
692
+ end
693
+ end
694
+
695
+ def run_workflow(args)
696
+ options = {}
697
+ optparse = OptionParser.new do|opts|
698
+ opts.banner = subcommand_usage("run-workflow", "[name]", "[workflow]")
699
+ build_common_options(opts, options, [:json, :dry_run, :remote])
700
+ end
701
+ if args.count < 2
702
+ puts "\n#{optparse}\n\n"
703
+ exit 1
704
+ end
705
+ optparse.parse!(args)
706
+ connect(options)
707
+ host = find_host_by_name_or_id(args[0])
708
+ workflow = find_workflow_by_name(args[1])
709
+ task_types = @tasks_interface.task_types()
710
+ editable_options = []
711
+ workflow['taskSetTasks'].sort{|a,b| a['taskOrder'] <=> b['taskOrder']}.each do |task_set_task|
712
+ task_type_id = task_set_task['task']['taskType']['id']
713
+ task_type = task_types['taskTypes'].find{ |current_task_type| current_task_type['id'] == task_type_id}
714
+ task_opts = task_type['optionTypes'].select { |otype| otype['editable']}
715
+ if !task_opts.nil? && !task_opts.empty?
716
+ editable_options += task_opts.collect do |task_opt|
717
+ new_task_opt = task_opt.clone
718
+ new_task_opt['fieldContext'] = "#{task_set_task['id']}.#{new_task_opt['fieldContext']}"
719
+ end
720
+ end
721
+ end
722
+ params = options[:options] || {}
723
+
724
+ if params.empty? && !editable_options.empty?
725
+ puts optparse
726
+ option_lines = editable_options.collect {|it| "\t-O #{it['fieldContext'] ? (it['fieldContext'] + '.') : ''}#{it['fieldName']}=\"value\"" }.join("\n")
727
+ puts "\nAvailable Options:\n#{option_lines}\n\n"
728
+ exit 1
729
+ end
730
+
731
+ workflow_payload = {taskSet: {"#{workflow['id']}" => params }}
732
+ begin
733
+ if options[:dry_run]
734
+ print_dry_run @servers_interface.dry.workflow(host['id'],workflow['id'], workflow_payload)
735
+ return
736
+ end
737
+ json_response = @servers_interface.workflow(host['id'],workflow['id'], workflow_payload)
738
+ if options[:json]
739
+ print JSON.pretty_generate(json_response)
740
+ print "\n"
741
+ else
742
+ puts "Running workflow..."
743
+ end
744
+ rescue RestClient::Exception => e
745
+ print_rest_exception(e, options)
746
+ exit 1
747
+ end
748
+ end
749
+
750
+ private
751
+
752
+ def find_host_by_id(id)
753
+ begin
754
+ json_response = @servers_interface.get(id.to_i)
755
+ return json_response['server']
756
+ rescue RestClient::Exception => e
757
+ if e.response && e.response.code == 404
758
+ print_red_alert "Host not found by id #{id}"
759
+ exit 1
760
+ else
761
+ raise e
762
+ end
763
+ end
764
+ end
765
+
766
+ def find_host_by_name(name)
767
+ results = @servers_interface.get({name: name})
768
+ if results['servers'].empty?
769
+ print_red_alert "Server not found by name #{name}"
770
+ exit 1
771
+ elsif results['servers'].size > 1
772
+ print_red_alert "Multiple Servers exist with the name #{name}. Try using id instead"
773
+ exit 1
774
+ end
775
+ return results['servers'][0]
776
+ end
777
+
778
+ def find_host_by_name_or_id(val)
779
+ if val.to_s =~ /\A\d{1,}\Z/
780
+ return find_host_by_id(val)
781
+ else
782
+ return find_host_by_name(val)
783
+ end
784
+ end
785
+
786
+ def find_zone_by_name_or_id(group_id, val)
787
+ zone = nil
788
+ if val.to_s =~ /\A\d{1,}\Z/
789
+ json_results = @clouds_interface.get(val.to_i)
790
+ zone = json_results['zone']
791
+ if zone.nil?
792
+ print_red_alert "Cloud not found by id #{val}"
793
+ exit 1
794
+ end
795
+ else
796
+ json_results = @clouds_interface.get({groupId: group_id, name: val})
797
+ zone = json_results['zones'] ? json_results['zones'][0] : nil
798
+ if zone.nil?
799
+ print_red_alert "Cloud not found by name #{val}"
800
+ exit 1
801
+ end
802
+ end
803
+ return zone
804
+ end
805
+
806
+ def find_server_type(zone, name)
807
+ server_type = zone['serverTypes'].select do |sv_type|
808
+ (sv_type['name'].downcase == name.downcase || sv_type['code'].downcase == name.downcase) && sv_type['creatable'] == true
809
+ end
810
+ if server_type.nil?
811
+ print_red_alert "Server Type Not Selectable"
812
+ end
813
+ return server_type
814
+ end
815
+
816
+ def cloud_type_for_id(id)
817
+ cloud_types = @clouds_interface.cloud_types['zoneTypes']
818
+ cloud_type = cloud_types.find { |z| z['id'].to_i == id.to_i}
819
+ if cloud_type.nil?
820
+ print_red_alert "Cloud Type not found by id #{id}"
821
+ exit 1
822
+ end
823
+ return cloud_type
824
+ end
825
+
826
+ def find_workflow_by_name(name)
827
+ task_set_results = @task_sets_interface.get(name)
828
+ if !task_set_results['taskSets'].nil? && !task_set_results['taskSets'].empty?
829
+ return task_set_results['taskSets'][0]
830
+ else
831
+ print_red_alert "Workflow not found by name #{name}"
832
+ exit 1
833
+ end
834
+ end
835
+
836
+ def print_servers_table(servers, opts={})
837
+ table_color = opts[:color] || cyan
838
+ rows = servers.collect do |server|
839
+ {
840
+ id: server['id'],
841
+ name: server['name'],
842
+ platform: server['serverOs'] ? server['serverOs']['name'].upcase : 'N/A',
843
+ cloud: server['zone'] ? server['zone']['name'] : '',
844
+ type: server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged',
845
+ nodes: server['containers'] ? server['containers'].size : '',
846
+ status: format_host_status(server, table_color),
847
+ power: format_server_power_state(server, table_color)
848
+ }
849
+ end
850
+ columns = [:id, :name, :type, :cloud, :nodes, :status, :power]
851
+ print table_color
852
+ tp rows, columns
853
+ print reset
854
+ end
855
+
856
+ def format_server_power_state(server, return_color=cyan)
857
+ out = ""
858
+ if server['powerState'] == 'on'
859
+ out << "#{green}ON#{return_color}"
860
+ elsif server['powerState'] == 'off'
861
+ out << "#{red}OFF#{return_color}"
862
+ else
863
+ out << "#{white}#{server['powerState'].upcase}#{return_color}"
864
+ end
865
+ out
866
+ end
867
+
868
+ def format_host_status(server, return_color=cyan)
869
+ out = ""
870
+ status_string = server['status']
871
+ # todo: colorize, upcase?
872
+ out << status_string.to_s
873
+ out
874
+ end
630
875
 
631
876
  end