morpheus-cli 0.9.9 → 0.9.10

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