morpheus-cli 0.9.9 → 0.9.10

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/morpheus/api/api_client.rb +4 -0
  3. data/lib/morpheus/api/app_templates_interface.rb +74 -0
  4. data/lib/morpheus/api/instance_types_interface.rb +9 -0
  5. data/lib/morpheus/api/instances_interface.rb +16 -0
  6. data/lib/morpheus/api/roles_interface.rb +37 -1
  7. data/lib/morpheus/cli.rb +4 -1
  8. data/lib/morpheus/cli/accounts.rb +82 -58
  9. data/lib/morpheus/cli/app_templates.rb +908 -0
  10. data/lib/morpheus/cli/apps.rb +226 -187
  11. data/lib/morpheus/cli/cli_command.rb +57 -30
  12. data/lib/morpheus/cli/clouds.rb +50 -65
  13. data/lib/morpheus/cli/deployments.rb +18 -33
  14. data/lib/morpheus/cli/deploys.rb +1 -3
  15. data/lib/morpheus/cli/groups.rb +54 -38
  16. data/lib/morpheus/cli/hosts.rb +86 -80
  17. data/lib/morpheus/cli/instance_types.rb +42 -29
  18. data/lib/morpheus/cli/instances.rb +192 -69
  19. data/lib/morpheus/cli/key_pairs.rb +70 -87
  20. data/lib/morpheus/cli/license.rb +7 -9
  21. data/lib/morpheus/cli/load_balancers.rb +23 -53
  22. data/lib/morpheus/cli/mixins/accounts_helper.rb +7 -8
  23. data/lib/morpheus/cli/mixins/print_helper.rb +67 -0
  24. data/lib/morpheus/cli/mixins/provisioning_helper.rb +461 -0
  25. data/lib/morpheus/cli/option_types.rb +71 -18
  26. data/lib/morpheus/cli/roles.rb +725 -34
  27. data/lib/morpheus/cli/security_group_rules.rb +50 -70
  28. data/lib/morpheus/cli/security_groups.rb +61 -48
  29. data/lib/morpheus/cli/shell.rb +123 -14
  30. data/lib/morpheus/cli/tasks.rb +24 -59
  31. data/lib/morpheus/cli/users.rb +86 -71
  32. data/lib/morpheus/cli/version.rb +1 -1
  33. data/lib/morpheus/cli/virtual_images.rb +21 -51
  34. data/lib/morpheus/cli/workflows.rb +14 -29
  35. data/lib/morpheus/ext/nil_class.rb +5 -0
  36. data/lib/morpheus/formatters.rb +1 -0
  37. metadata +7 -3
  38. data/lib/morpheus/cli/error_handler.rb +0 -44
@@ -0,0 +1,908 @@
1
+ require 'io/console'
2
+ require 'rest_client'
3
+ require 'optparse'
4
+ require 'morpheus/cli/cli_command'
5
+ require 'morpheus/cli/option_types'
6
+ require 'json'
7
+
8
+ class Morpheus::Cli::AppTemplates
9
+ include Morpheus::Cli::CliCommand
10
+
11
+ def initialize()
12
+ @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
13
+ end
14
+
15
+ def connect(opts)
16
+ @access_token = Morpheus::Cli::Credentials.new(@appliance_name,@appliance_url).request_credentials()
17
+ if @access_token.empty?
18
+ print_red_alert "Invalid Credentials. Unable to acquire access token. Please verify your credentials and try again."
19
+ exit 1
20
+ end
21
+ @active_groups = ::Morpheus::Cli::Groups.load_group_file
22
+ @api_client = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url)
23
+ @app_templates_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).app_templates
24
+ @groups_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).groups
25
+ @instance_types_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).instance_types
26
+ @options_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).options
27
+ end
28
+
29
+ def handle(args)
30
+ usage = "Usage: morpheus app-templates [list,details,add,update,remove,add-instance,remove-instance,connect-tiers,available-tiers] [name]"
31
+ if args.empty?
32
+ puts "\n#{usage}\n\n"
33
+ exit 1
34
+ end
35
+
36
+ case args[0]
37
+ when 'list'
38
+ list(args[1..-1])
39
+ when 'details'
40
+ details(args[1..-1])
41
+ when 'add'
42
+ add(args[1..-1])
43
+ when 'update'
44
+ update(args[1..-1])
45
+ when 'add-instance'
46
+ add_instance(args[1..-1])
47
+ when 'remove-instance'
48
+ remove_instance(args[1..-1])
49
+ when 'connect-tiers'
50
+ connect_tiers(args[1..-1])
51
+ when 'remove'
52
+ remove(args[1..-1])
53
+ when 'available-tiers'
54
+ available_tiers(args[1..-1])
55
+ when 'available-types'
56
+ available_types(args[1..-1])
57
+ else
58
+ puts "\n#{usage}\n\n"
59
+ exit 127
60
+ end
61
+ end
62
+
63
+ def list(args)
64
+ options = {}
65
+ optparse = OptionParser.new do|opts|
66
+ build_common_options(opts, options, [:list, :json])
67
+ end
68
+ optparse.parse(args)
69
+ connect(options)
70
+ begin
71
+ params = {}
72
+ [:phrase, :offset, :max, :sort, :direction].each do |k|
73
+ params[k] = options[k] unless options[k].nil?
74
+ end
75
+
76
+ json_response = @app_templates_interface.list(params)
77
+ app_templates = json_response['appTemplates']
78
+ if options[:json]
79
+ print JSON.pretty_generate(json_response)
80
+ print "\n"
81
+ else
82
+ print "\n" ,cyan, bold, "Morpheus App Templates\n","==================", reset, "\n\n"
83
+ if app_templates.empty?
84
+ puts yellow,"No app templates found.",reset
85
+ else
86
+ print_app_templates_table(app_templates)
87
+ end
88
+ print reset,"\n\n"
89
+ end
90
+ rescue RestClient::Exception => e
91
+ print_rest_exception(e, options)
92
+ exit 1
93
+ end
94
+ end
95
+
96
+ def details(args)
97
+ usage = "Usage: morpheus app-templates details [name]"
98
+ options = {}
99
+ optparse = OptionParser.new do|opts|
100
+ opts.banner = usage
101
+ opts.on( '-c', '--config', "Display Config Data" ) do |val|
102
+ options[:config] = true
103
+ end
104
+ build_common_options(opts, options, [:json])
105
+ end
106
+ optparse.parse(args)
107
+ if args.count < 1
108
+ puts "\n#{usage}\n\n"
109
+ exit 1
110
+ end
111
+ name = args[0]
112
+ connect(options)
113
+ begin
114
+
115
+ app_template = find_app_template_by_name(name)
116
+ exit 1 if app_template.nil?
117
+
118
+ json_response = @app_templates_interface.get(app_template['id'])
119
+ app_template = json_response['appTemplate']
120
+
121
+ if options[:json]
122
+ print JSON.pretty_generate(json_response)
123
+ print "\n"
124
+ else
125
+ print "\n" ,cyan, bold, "App Template Details\n","==================", reset, "\n\n"
126
+ print cyan
127
+ puts "ID: #{app_template['id']}"
128
+ puts "Name: #{app_template['name']}"
129
+ #puts "Category: #{app_template['category']}"
130
+ puts "Account: #{app_template['account'] ? app_template['account']['name'] : ''}"
131
+ instance_type_names = (app_template['instanceTypes'] || []).collect {|it| it['name'] }
132
+ #puts "Instance Types: #{instance_type_names.join(', ')}"
133
+ config = app_template['config']['tierView']
134
+ tiers = config['nodes'].select {|node| node['data']['type'] == "tier" }
135
+ if tiers.empty?
136
+ puts yellow,"0 Tiers",reset
137
+ else
138
+ tiers.each do |tier|
139
+ instances = config['nodes'].select {|node| node['data']['type'] == "instance" && node['data']['parent'] == tier['data']['id'] }.sort {|x,y| x['data']['index'].to_i <=> y['data']['index'].to_i }
140
+ print "\n"
141
+ print cyan, "= #{tier['data']['name']}\n"
142
+ instances.each do |instance|
143
+ instance_id = instance['data']['id'].sub('newinstance-', '')
144
+ print green, " - #{instance['data']['typeName']} (#{instance_id})\n",reset
145
+ end
146
+
147
+ end
148
+ end
149
+ print cyan
150
+
151
+ if options[:config]
152
+ puts "\nConfig:"
153
+ puts JSON.pretty_generate(config)
154
+ end
155
+
156
+ print reset,"\n\n"
157
+ end
158
+ rescue RestClient::Exception => e
159
+ print_rest_exception(e, options)
160
+ exit 1
161
+ end
162
+ end
163
+
164
+ def add(args)
165
+ usage = "Usage: morpheus app-templates add"
166
+ options = {}
167
+ optparse = OptionParser.new do|opts|
168
+ opts.banner = usage
169
+ # opts.on( '-g', '--group GROUP', "Group Name" ) do |group|
170
+ # options[:options] ||= {}
171
+ # options[:options]['group'] = group
172
+ # end
173
+ build_common_options(opts, options, [:options, :json])
174
+ end
175
+ optparse.parse(args)
176
+ connect(options)
177
+ begin
178
+
179
+ params = Morpheus::Cli::OptionTypes.prompt(add_app_template_option_types, options[:options], @api_client, options[:params])
180
+
181
+ #puts "parsed params is : #{params.inspect}"
182
+ app_template_keys = ['name']
183
+ app_template_payload = params.select {|k,v| app_template_keys.include?(k) }
184
+
185
+ group = nil
186
+ if params['group'].to_s != ''
187
+ group = find_group_by_name(params['group'])
188
+ exit 1 if group.nil?
189
+ #app_template_payload['siteId'] = {id: group['id']}
190
+ end
191
+ config = {
192
+ nodes: []
193
+ }
194
+ request_payload = {appTemplate: app_template_payload}
195
+ request_payload['siteId'] = group['id'] if group
196
+ request_payload['config'] = config
197
+ json_response = @app_templates_interface.create(request_payload)
198
+
199
+ if options[:json]
200
+ print JSON.pretty_generate(json_response)
201
+ print "\n"
202
+ else
203
+ print_green_success "Added app template #{app_template_payload['name']}"
204
+ details_options = [app_template_payload["name"]]
205
+ details(details_options)
206
+ end
207
+
208
+ rescue RestClient::Exception => e
209
+ print_rest_exception(e, options)
210
+ exit 1
211
+ end
212
+ end
213
+
214
+ def update(args)
215
+ usage = "Usage: morpheus app-templates update [name] [options]"
216
+ options = {}
217
+ optparse = OptionParser.new do|opts|
218
+ opts.banner = usage
219
+ build_common_options(opts, options, [:options, :json])
220
+ end
221
+ optparse.parse(args)
222
+
223
+ if args.count < 1
224
+ puts "\n#{usage}\n\n"
225
+ exit 1
226
+ end
227
+ name = args[0]
228
+
229
+ connect(options)
230
+
231
+ begin
232
+
233
+ app_template = find_app_template_by_name(name)
234
+ exit 1 if app_template.nil?
235
+
236
+ #params = Morpheus::Cli::OptionTypes.prompt(update_app_template_option_types, options[:options], @api_client, options[:params])
237
+ params = options[:options] || {}
238
+
239
+ if params.empty?
240
+ puts "\n#{usage}\n\n"
241
+ option_lines = update_app_template_option_types.collect {|it| "\t-O #{it['fieldName']}=\"value\"" }.join("\n")
242
+ puts "\nAvailable Options:\n#{option_lines}\n\n"
243
+ exit 1
244
+ end
245
+
246
+ #puts "parsed params is : #{params.inspect}"
247
+ app_template_keys = ['name']
248
+ app_template_payload = params.select {|k,v| app_template_keys.include?(k) }
249
+
250
+ group = nil
251
+ if params['group'].to_s != ''
252
+ group = find_group_by_name(params['group'])
253
+ exit 1 if group.nil?
254
+ #app_template_payload['siteId'] = {id: group['id']}
255
+ end
256
+ config = app_template['config'] # {}
257
+ request_payload = {appTemplate: app_template_payload}
258
+ if group
259
+ request_payload['siteId'] = group['id']
260
+ else
261
+ request_payload['siteId'] = app_template['config']['siteId']
262
+ end
263
+ # request_payload['config'] = config['tierView']
264
+ request_payload['config'] = config
265
+ json_response = @app_templates_interface.update(app_template['id'], request_payload)
266
+
267
+
268
+ if options[:json]
269
+ print JSON.pretty_generate(json_response)
270
+ print "\n"
271
+ else
272
+ print_green_success "Updated app template #{app_template_payload['name']}"
273
+ details_options = [app_template_payload["name"] || app_template['name']]
274
+ details(details_options)
275
+ end
276
+
277
+ rescue RestClient::Exception => e
278
+ print_rest_exception(e, options)
279
+ exit 1
280
+ end
281
+ end
282
+
283
+ def remove(args)
284
+ usage = "Usage: morpheus app-templates remove [name]"
285
+ options = {}
286
+ optparse = OptionParser.new do|opts|
287
+ opts.banner = usage
288
+ build_common_options(opts, options, [:auto_confirm, :json])
289
+ end
290
+ optparse.parse(args)
291
+
292
+ if args.count < 1
293
+ puts "\n#{usage}\n\n"
294
+ exit 1
295
+ end
296
+ name = args[0]
297
+
298
+ connect(options)
299
+ begin
300
+ # allow finding by ID since name is not unique!
301
+ app_template = ((name.to_s =~ /\A\d{1,}\Z/) ? find_app_template_by_id(name) : find_app_template_by_name(name) )
302
+ exit 1 if app_template.nil?
303
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the app template #{app_template['name']}?")
304
+ exit
305
+ end
306
+ json_response = @app_templates_interface.destroy(app_template['id'])
307
+
308
+ if options[:json]
309
+ print JSON.pretty_generate(json_response)
310
+ print "\n"
311
+ else
312
+ print_green_success "App Template #{app_template['name']} removed"
313
+ end
314
+
315
+ rescue RestClient::Exception => e
316
+ print_rest_exception(e, options)
317
+ exit 1
318
+ end
319
+ end
320
+
321
+ def add_instance(args)
322
+ usage = "Usage: morpheus app-templates add-instance [name] [tier] [instance-type]"
323
+ options = {}
324
+ optparse = OptionParser.new do|opts|
325
+ opts.banner = usage
326
+ # opts.on( '-g', '--group GROUP', "Group" ) do |val|
327
+ # options[:group] = val
328
+ # end
329
+ # opts.on( '-c', '--cloud CLOUD', "Cloud" ) do |val|
330
+ # options[:cloud] = val
331
+ # end
332
+ build_common_options(opts, options, [:json])
333
+ end
334
+ optparse.parse(args)
335
+
336
+ if args.count < 3
337
+ puts "\n#{optparse}\n\n"
338
+ exit 1
339
+ end
340
+
341
+ connect(options)
342
+
343
+ name = args[0]
344
+ tier_name = args[1]
345
+ instance_type_code = args[2]
346
+
347
+ app_template = find_app_template_by_name(name)
348
+ exit 1 if app_template.nil?
349
+
350
+ instance_type = find_instance_type_by_code(instance_type_code)
351
+ if instance_type.nil?
352
+ exit 1
353
+ end
354
+
355
+ groupId = app_template['config']['siteId']
356
+ # groupId = nil
357
+ # if !options[:group].nil?
358
+ # group = find_group_by_name(options[:group])
359
+ # if !group.nil?
360
+ # groupId = group
361
+ # end
362
+ # else
363
+ # groupId = @active_groups[@appliance_name.to_sym]
364
+ # end
365
+
366
+ if groupId.nil?
367
+ #puts "Group not found or specified! \n #{optparse}"
368
+ print_red_alert("Group not found or specified for this template!")
369
+ puts "#{optparse}"
370
+ exit 1
371
+ end
372
+
373
+ cloud_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'type' => 'select', 'fieldLabel' => 'Cloud', 'optionSource' => 'clouds', 'required' => true, 'description' => 'Select Cloud.'}],options[:options],@api_client,{groupId: groupId})
374
+ cloud = cloud_prompt['cloud']
375
+
376
+ # if options[:cloud].nil?
377
+ # #puts "Cloud not specified! \n #{optparse}"
378
+ # print_red_alert("Cloud not specified!")
379
+ # puts "#{optparse}"
380
+ # exit 1
381
+ # end
382
+ # cloud = find_cloud_by_name(groupId,options[:cloud])
383
+ # if cloud.nil?
384
+ # #puts "Cloud not found! \n #{optparse}"
385
+ # #print_red_alert("Cloud not found!")
386
+ # puts "#{optparse}"
387
+ # exit 1
388
+ # end
389
+
390
+
391
+ instance_option_types = [{'fieldName' => 'name', 'fieldLabel' => 'Instance Name', 'type' => 'text'}]
392
+ instance_option_values = Morpheus::Cli::OptionTypes.prompt(instance_option_types, options[:options], @api_client, options[:params])
393
+ instance_name = instance_option_values['name'] || ''
394
+
395
+ # copied from instances command, this payload isn't used
396
+ payload = {
397
+ :servicePlan => nil,
398
+ zoneId: cloud,
399
+ :instance => {
400
+ :name => instance_name,
401
+ :site => {
402
+ :id => groupId
403
+ },
404
+ :instanceType => {
405
+ :code => instance_type_code
406
+ }
407
+ }
408
+ }
409
+
410
+ version_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'version', 'type' => 'select', 'fieldLabel' => 'Version', 'optionSource' => 'instanceVersions', 'required' => true, 'skipSingleOption' => true, 'description' => 'Select which version of the instance type to be provisioned.'}],options[:options],@api_client,{groupId: groupId, cloudId: cloud, instanceTypeId: instance_type['id']})
411
+ layout_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'layout', 'type' => 'select', 'fieldLabel' => 'Layout', 'optionSource' => 'layoutsForCloud', 'required' => true, 'description' => 'Select which configuration of the instance type to be provisioned.'}],options[:options],@api_client,{groupId: groupId, cloudId: cloud, instanceTypeId: instance_type['id'], version: version_prompt['version']})
412
+ layout_id = layout_prompt['layout']
413
+ plan_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'servicePlan', 'type' => 'select', 'fieldLabel' => 'Plan', 'optionSource' => 'instanceServicePlans', 'required' => true, 'description' => 'Choose the appropriately sized plan for this instance'}],options[:options],@api_client,{groupId: groupId, zoneId: cloud, instanceTypeId: instance_type['id'], layoutId: layout_id, version: version_prompt['version']})
414
+ payload[:servicePlan] = plan_prompt['servicePlan']
415
+
416
+ layout = instance_type['instanceTypeLayouts'].find{ |lt| lt['id'].to_i == layout_id.to_i}
417
+ instance_type['instanceTypeLayouts'].sort! { |x,y| y['sortOrder'] <=> x['sortOrder'] }
418
+
419
+ payload[:instance][:layout] = {id: layout['id']}
420
+
421
+ type_payload = {}
422
+ if !layout['optionTypes'].nil? && !layout['optionTypes'].empty?
423
+ type_payload = Morpheus::Cli::OptionTypes.prompt(layout['optionTypes'],options[:options],@api_client,{groupId: groupId, cloudId: cloud, zoneId: cloud, instanceTypeId: instance_type['id'], version: version_prompt['version']})
424
+ elsif !instance_type['optionTypes'].nil? && !instance_type['optionTypes'].empty?
425
+ type_payload = Morpheus::Cli::OptionTypes.prompt(instance_type['optionTypes'],options[:options],@api_client,{groupId: groupId, cloudId: cloud, zoneId: cloud, instanceTypeId: instance_type['id'], version: version_prompt['version']})
426
+ end
427
+ if !type_payload['config'].nil?
428
+ payload.merge!(type_payload['config'])
429
+ end
430
+
431
+ provision_payload = {}
432
+ if !layout['provisionType'].nil? && !layout['provisionType']['optionTypes'].nil? && !layout['provisionType']['optionTypes'].empty?
433
+ puts "Checking for option Types"
434
+ provision_payload = Morpheus::Cli::OptionTypes.prompt(layout['provisionType']['optionTypes'],options[:options],@api_client,{groupId: groupId, cloudId: cloud, zoneId: cloud, instanceTypeId: instance_type['id'], version: version_prompt['version']})
435
+ end
436
+
437
+ if !provision_payload.nil? && !provision_payload['config'].nil?
438
+ payload.merge!(provision_payload['config'])
439
+ end
440
+ # if !provision_payload.nil? && !provision_payload['server'].nil?
441
+ # payload[:server] = provision_payload['server']
442
+ # end
443
+
444
+
445
+ config = app_template['config']['tierView']
446
+
447
+ config['nodes'] ||= []
448
+
449
+ tier = config['nodes'].find {|node|
450
+ node["data"] && node["data"]["type"] == "tier" && node["data"]["id"] == "newtier-#{tier_name}"
451
+ }
452
+ if !tier
453
+ tier = {
454
+ "classes"=>"tier newtier-#{tier_name}",
455
+ "data"=>{"id"=>"newtier-#{tier_name}", "name"=> tier_name, "type"=>"tier"},
456
+ "grabbable"=>true, "group"=>"nodes","locked"=>false,
457
+ #"position"=>{"x"=>-2.5, "y"=>-45},
458
+ "removed"=>false, "selectable"=>true, "selected"=>false
459
+ }
460
+ config['nodes'] << tier
461
+ end
462
+
463
+ instance_id = generate_id()
464
+
465
+ instance_type_node = {
466
+ "classes"=>"instance newinstance-#{instance_id} #{instance_type['code']}",
467
+ "data"=>{
468
+ "controlName" => "instance.layout.id",
469
+ "id"=>"newinstance-#{instance_id}",
470
+ "nodeId"=>["newinstance-#{instance_id}"], # not sure what this is for..
471
+ "index"=>nil,
472
+ "instance.layout.id"=>layout_id.to_s,
473
+ "instance.name"=>instance_name,
474
+ "instanceType"=>instance_type['code'],
475
+ "isPlaced"=>true,
476
+ "name"=> instance_name,
477
+ "parent"=>tier['data']['id'],
478
+ "type"=>"instance",
479
+ "typeName"=>instance_type['name'],
480
+ "servicePlan"=>plan_prompt['servicePlan'].to_s,
481
+ # "servicePlanOptions.maxCpu": "",
482
+ # "servicePlanOptions.maxCpuId": nil,
483
+ # "servicePlanOptions.maxMemory": "",
484
+ # "servicePlanOptions.maxMemoryId": nil,
485
+
486
+ # "volumes.datastoreId": nil,
487
+ # "volumes.name": "root",
488
+ # "volumes.rootVolume": "true",
489
+ # "volumes.size": "5",
490
+ # "volumes.sizeId": "5",
491
+ # "volumes.storageType": nil,
492
+
493
+ "version"=>version_prompt['version'],
494
+ "siteId"=>groupId.to_s,
495
+ "zoneId"=>cloud.to_s
496
+ },
497
+ "grabbable"=>true, "group"=>"nodes", "locked"=>false,
498
+ #"position"=>{"x"=>-79.83254449505226, "y"=>458.33806818181824},
499
+ "removed"=>false, "selectable"=>true, "selected"=>false
500
+ }
501
+
502
+ if !type_payload['config'].nil?
503
+ instance_type_node['data'].merge!(type_payload['config'])
504
+ end
505
+
506
+ if !provision_payload.nil? && !provision_payload['config'].nil?
507
+ instance_type_node['data'].merge!(provision_payload['config'])
508
+ end
509
+
510
+ config['nodes'].push(instance_type_node)
511
+
512
+ # re-index nodes for this tier
513
+ tier_instances = config['nodes'].select {|node| node['data']['parent'] == tier['data']['id'] }
514
+ tier_instances.each_with_index do |node, idx|
515
+ node['data']['index'] = idx
516
+ end
517
+
518
+ request_payload = {appTemplate: {} }
519
+ request_payload['siteId'] = app_template['config']['siteId']
520
+ # request_payload['config'] = config['tierView']
521
+ request_payload['config'] = config
522
+
523
+ begin
524
+ json_response = @app_templates_interface.update(app_template['id'], request_payload)
525
+
526
+ if options[:json]
527
+ print JSON.pretty_generate(json_response)
528
+ print "\n"
529
+ else
530
+ print_green_success "Added instance type to app template #{app_template['name']}"
531
+ details_options = [app_template['name']]
532
+ details(details_options)
533
+ end
534
+
535
+ rescue RestClient::Exception => e
536
+ print_rest_exception(e, options)
537
+ exit 1
538
+ end
539
+
540
+ end
541
+
542
+ def remove_instance(args)
543
+ usage = "Usage: morpheus app-templates remove-instance [name] [instance-id]"
544
+ options = {}
545
+ optparse = OptionParser.new do|opts|
546
+ opts.banner = usage
547
+ build_common_options(opts, options, [:auto_confirm, :json])
548
+ end
549
+ optparse.parse(args)
550
+
551
+ if args.count < 2
552
+ puts "\n#{usage}\n\n"
553
+ exit 1
554
+ end
555
+ name = args[0]
556
+ instance_id = args[1]
557
+
558
+ connect(options)
559
+
560
+ begin
561
+
562
+ app_template = find_app_template_by_name(name)
563
+ exit 1 if app_template.nil?
564
+
565
+ config = app_template['config']['tierView']
566
+
567
+ config['nodes'] ||= []
568
+
569
+ instance_node = config['nodes'].find { |node|
570
+ node["data"] && node["data"]["type"] == "instance" && node["data"]["id"] == "newinstance-#{instance_id}"
571
+ }
572
+
573
+ if instance_node.nil?
574
+ print_red_alert "Instance not found by id #{instance_id}"
575
+ exit 1
576
+ end
577
+
578
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the app template #{instance_node['data']['typeName']} instance #{instance_id}?")
579
+ exit
580
+ end
581
+
582
+ tier = config['nodes'].find {|node|
583
+ node["data"] && node["data"]["type"] == "tier" && node["data"]["id"] == instance_node['data']['parent']
584
+ }
585
+
586
+ if tier.nil?
587
+ print_red_alert "Parent Tier not found for instance id #{instance_id}!"
588
+ exit 1
589
+ end
590
+
591
+ # remove the one node
592
+ config['nodes'] = config['nodes'].reject {|node|
593
+ node["data"] && node["data"]["type"] == "instance" && node["data"]["id"] == "newinstance-#{instance_id}"
594
+ }
595
+
596
+
597
+ # re-index nodes for this tier
598
+ tier_instances = config['nodes'].select {|node| node['data']['parent'] == tier['data']['id'] }
599
+ tier_instances.each_with_index do |node, idx|
600
+ node['data']['index'] = idx
601
+ end
602
+
603
+ request_payload = {appTemplate: {} }
604
+ request_payload['siteId'] = app_template['config']['siteId']
605
+ # request_payload['config'] = config['tierView']
606
+ request_payload['config'] = config
607
+ json_response = @app_templates_interface.update(app_template['id'], request_payload)
608
+
609
+
610
+ if options[:json]
611
+ print JSON.pretty_generate(json_response)
612
+ print "\n"
613
+ else
614
+ print_green_success "Added instance type to app template #{app_template['name']}"
615
+ details_options = [app_template['name']]
616
+ details(details_options)
617
+ end
618
+
619
+ rescue RestClient::Exception => e
620
+ print_rest_exception(e, options)
621
+ exit 1
622
+ end
623
+ end
624
+
625
+ def connect_tiers(args)
626
+ usage = "Usage: morpheus app-templates connect-tiers [name] [tier1] [tier2]"
627
+ options = {}
628
+ optparse = OptionParser.new do|opts|
629
+ opts.banner = usage
630
+ build_common_options(opts, options, [:json])
631
+ end
632
+ optparse.parse(args)
633
+
634
+ if args.count < 3
635
+ puts "\n#{usage}\n\n"
636
+ exit 1
637
+ end
638
+ name = args[0]
639
+ tier1_name = args[1]
640
+ tier2_name = args[2]
641
+
642
+ connect(options)
643
+
644
+ begin
645
+
646
+ app_template = find_app_template_by_name(name)
647
+ exit 1 if app_template.nil?
648
+
649
+ config = app_template['config']['tierView']
650
+
651
+ config['nodes'] ||= []
652
+
653
+ tier1 = config['nodes'].find {|node|
654
+ node["data"] && node["data"]["type"] == "tier" && node["data"]["id"] == "newtier-#{tier1_name}"
655
+ }
656
+ if tier1.nil?
657
+ print_red_alert "Tier not found by name #{tier1_name}!"
658
+ exit 1
659
+ end
660
+
661
+ tier2 = config['nodes'].find {|node|
662
+ node["data"] && node["data"]["type"] == "tier" && node["data"]["id"] == "newtier-#{tier2_name}"
663
+ }
664
+ if tier2.nil?
665
+ print_red_alert "Tier not found by name #{tier2_name}!"
666
+ exit 1
667
+ end
668
+
669
+ config['edges'] ||= []
670
+
671
+ found_edge = config['edges'].find {|edge|
672
+ (edge['data']['source'] == "newtier-#{tier1_name}" && edge['data']['target'] == "newtier-#{tier2_name}") &&
673
+ (edge['data']['target'] == "newtier-#{tier2_name}" && edge['data']['source'] == "newtier-#{tier1_name}")
674
+ }
675
+
676
+ if found_edge
677
+ puts yellow,"Tiers #{tier1_name} and #{tier2_name} are already connected.",reset
678
+ exit
679
+ end
680
+
681
+ # not sure how this id is being generated in the ui exactly
682
+ new_edge_index = (1..999).find {|i|
683
+ !config['edges'].find {|edge| edge['data']['id'] == "ele#{i}" }
684
+ }
685
+ new_edge = {
686
+ "classes"=>"",
687
+ "data"=>{"id"=>"ele#{new_edge_index}", "source"=>tier1['data']['id'], "target"=>tier2['data']['id']},
688
+ "grabbable"=>true, "group"=>"edges", "locked"=>false,
689
+ #"position"=>{},
690
+ "removed"=>false, "selectable"=>true, "selected"=>false
691
+ }
692
+
693
+ config['edges'].push(new_edge)
694
+
695
+
696
+ request_payload = {appTemplate: {} }
697
+ request_payload['siteId'] = app_template['config']['siteId']
698
+ # request_payload['config'] = config['tierView']
699
+ request_payload['config'] = config
700
+ json_response = @app_templates_interface.update(app_template['id'], request_payload)
701
+
702
+
703
+ if options[:json]
704
+ print JSON.pretty_generate(json_response)
705
+ print "\n"
706
+ else
707
+ print_green_success "Connected tiers for app template #{app_template['name']}"
708
+ details_options = [app_template['name']]
709
+ details(details_options)
710
+ end
711
+
712
+ rescue RestClient::Exception => e
713
+ print_rest_exception(e, options)
714
+ exit 1
715
+ end
716
+ end
717
+
718
+ def available_tiers(args)
719
+ options = {}
720
+ optparse = OptionParser.new do|opts|
721
+ build_common_options(opts, options, [:json])
722
+ end
723
+ optparse.parse(args)
724
+ connect(options)
725
+ params = {}
726
+
727
+ begin
728
+ json_response = @app_templates_interface.list_tiers(params)
729
+ tiers = json_response['tiers']
730
+ if options[:json]
731
+ print JSON.pretty_generate(json_response)
732
+ print "\n"
733
+ else
734
+ print "\n" ,cyan, bold, "Tiers\n","==================", reset, "\n\n"
735
+ if tiers.empty?
736
+ puts yellow,"No tiers found.",reset
737
+ else
738
+ rows = tiers.collect do |tier|
739
+ {
740
+ id: tier['id'],
741
+ name: tier['name'],
742
+ }
743
+ end
744
+ print cyan
745
+ tp rows, [:name]
746
+ print reset
747
+ end
748
+ print reset,"\n\n"
749
+ end
750
+ rescue RestClient::Exception => e
751
+ print_rest_exception(e, options)
752
+ exit 1
753
+ end
754
+
755
+ end
756
+
757
+ def available_types(args)
758
+ options = {}
759
+ optparse = OptionParser.new do|opts|
760
+ build_common_options(opts, options, [:json])
761
+ end
762
+ optparse.parse(args)
763
+ connect(options)
764
+ params = {}
765
+
766
+ begin
767
+ json_response = @app_templates_interface.list_types(params)
768
+ instance_types = json_response['types']
769
+ if options[:json]
770
+ print JSON.pretty_generate(json_response)
771
+ print "\n"
772
+ else
773
+ print "\n" ,cyan, bold, "Instance Types\n","==================", reset, "\n\n"
774
+ if instance_types.empty?
775
+ puts yellow,"No instance types found.",reset
776
+ else
777
+ rows = instance_types.collect do |instance_type|
778
+ {
779
+ id: instance_type['id'],
780
+ code: instance_type['code'],
781
+ name: instance_type['name'],
782
+ }
783
+ end
784
+ print cyan
785
+ tp rows, [:code, :name]
786
+ print reset
787
+ end
788
+ print reset,"\n\n"
789
+ end
790
+ rescue RestClient::Exception => e
791
+ print_rest_exception(e, options)
792
+ exit 1
793
+ end
794
+
795
+ end
796
+
797
+ private
798
+
799
+
800
+ def add_app_template_option_types
801
+ [
802
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
803
+ {'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'text', 'required' => true, 'displayOrder' => 2},
804
+ ]
805
+ end
806
+
807
+ def update_app_template_option_types
808
+ add_app_template_option_types
809
+ end
810
+
811
+ def find_app_template_by_id(id)
812
+ begin
813
+ json_response = @app_templates_interface.get(id.to_i)
814
+ return json_response['appTemplate']
815
+ rescue RestClient::Exception => e
816
+ if e.response.code == 404
817
+ print_red_alert "App Template not found by id #{id}"
818
+ else
819
+ raise e
820
+ end
821
+ end
822
+ end
823
+
824
+ def find_app_template_by_name(name)
825
+ app_templates = @app_templates_interface.list({name: name.to_s})['appTemplates']
826
+ if app_templates.empty?
827
+ print_red_alert "App Template not found by name #{name}"
828
+ return nil
829
+ elsif app_templates.size > 1
830
+ print_red_alert "#{app_templates.size} app templates by name #{name}"
831
+ print_app_templates_table(app_templates, {color: red})
832
+ print reset,"\n\n"
833
+ return nil
834
+ else
835
+ return app_templates[0]
836
+ end
837
+ end
838
+
839
+ def find_group_by_name(name)
840
+ group_results = @groups_interface.get(name)
841
+ if group_results['groups'].empty?
842
+ print_red_alert "Group not found by name #{name}"
843
+ return nil
844
+ end
845
+ return group_results['groups'][0]
846
+ end
847
+
848
+ def find_cloud_by_name(groupId,name)
849
+ option_results = @options_interface.options_for_source('clouds',{groupId: groupId})
850
+ match = option_results['data'].find { |grp| grp['value'].to_s == name.to_s || grp['name'].downcase == name.downcase}
851
+ if match.nil?
852
+ print_red_alert "Cloud not found by name #{name}"
853
+ return nil
854
+ else
855
+ return match['value']
856
+ end
857
+ end
858
+
859
+ def find_instance_type_by_code(code)
860
+ results = @instance_types_interface.get({code: code})
861
+ if results['instanceTypes'].empty?
862
+ print_red_alert "Instance Type not found by code #{code}"
863
+ return nil
864
+ end
865
+ return results['instanceTypes'][0]
866
+ end
867
+
868
+ def find_instance_type_by_name(name)
869
+ results = @instance_types_interface.get({name: name})
870
+ if results['instanceTypes'].empty?
871
+ print_red_alert "Instance Type not found by name #{name}"
872
+ return nil
873
+ end
874
+ return results['instanceTypes'][0]
875
+ end
876
+
877
+ def print_app_templates_table(app_templates, opts={})
878
+ table_color = opts[:color] || cyan
879
+ rows = app_templates.collect do |app_template|
880
+ instance_type_names = (app_template['instanceTypes'] || []).collect {|it| it['name'] }.join(', ')
881
+ {
882
+ id: app_template['id'],
883
+ name: app_template['name'],
884
+ #code: app_template['code'],
885
+ instance_types: instance_type_names,
886
+ account: app_template['account'] ? app_template['account']['name'] : nil,
887
+ #dateCreated: format_local_dt(app_template['dateCreated'])
888
+ }
889
+ end
890
+
891
+ print table_color
892
+ tp rows, [
893
+ :id,
894
+ :name,
895
+ {:instance_types => {:display_name => "Instance Types"} },
896
+ :account,
897
+ #{:dateCreated => {:display_name => "Date Created"} }
898
+ ]
899
+ print reset
900
+ end
901
+
902
+ def generate_id(len=16)
903
+ id = ""
904
+ len.times { id << (1 + rand(9)).to_s }
905
+ id
906
+ end
907
+
908
+ end