morpheus-cli 2.10.0 → 2.10.1

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