morpheus-cli 5.4.0 → 5.4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/account_users_interface.rb +68 -0
  4. data/lib/morpheus/api/api_client.rb +55 -10
  5. data/lib/morpheus/api/audit_interface.rb +9 -0
  6. data/lib/morpheus/api/catalog_item_types_interface.rb +20 -0
  7. data/lib/morpheus/api/instances_interface.rb +49 -0
  8. data/lib/morpheus/api/load_balancer_monitors_interface.rb +9 -0
  9. data/lib/morpheus/api/load_balancer_pools_interface.rb +4 -4
  10. data/lib/morpheus/api/load_balancer_profiles_interface.rb +4 -5
  11. data/lib/morpheus/api/load_balancer_virtual_servers_interface.rb +13 -4
  12. data/lib/morpheus/api/load_balancers_interface.rb +5 -0
  13. data/lib/morpheus/api/network_routers_interface.rb +9 -0
  14. data/lib/morpheus/api/network_static_routes_interface.rb +36 -0
  15. data/lib/morpheus/api/ping_interface.rb +2 -0
  16. data/lib/morpheus/api/read_interface.rb +4 -3
  17. data/lib/morpheus/api/rest_interface.rb +3 -3
  18. data/lib/morpheus/api/secondary_read_interface.rb +1 -1
  19. data/lib/morpheus/api/secondary_rest_interface.rb +19 -19
  20. data/lib/morpheus/api/setup_interface.rb +4 -0
  21. data/lib/morpheus/api/snapshots_interface.rb +19 -0
  22. data/lib/morpheus/api/storage_server_types_interface.rb +14 -0
  23. data/lib/morpheus/api/storage_servers_interface.rb +9 -0
  24. data/lib/morpheus/api/storage_volume_types_interface.rb +9 -0
  25. data/lib/morpheus/api/storage_volumes_interface.rb +9 -0
  26. data/lib/morpheus/api/users_interface.rb +16 -63
  27. data/lib/morpheus/cli/cli_command.rb +253 -5
  28. data/lib/morpheus/cli/cli_registry.rb +1 -1
  29. data/lib/morpheus/cli/commands/alias_command.rb +1 -1
  30. data/lib/morpheus/cli/commands/apps.rb +14 -78
  31. data/lib/morpheus/cli/commands/audit.rb +188 -0
  32. data/lib/morpheus/cli/commands/blueprints_command.rb +1 -1
  33. data/lib/morpheus/cli/commands/catalog_item_types_command.rb +88 -0
  34. data/lib/morpheus/cli/commands/change_password_command.rb +4 -4
  35. data/lib/morpheus/cli/commands/clusters.rb +96 -58
  36. data/lib/morpheus/cli/commands/hosts.rb +27 -15
  37. data/lib/morpheus/cli/commands/image_builder_command.rb +4 -8
  38. data/lib/morpheus/cli/commands/instances.rb +359 -3
  39. data/lib/morpheus/cli/commands/integrations_command.rb +1 -12
  40. data/lib/morpheus/cli/commands/library_instance_types_command.rb +3 -0
  41. data/lib/morpheus/cli/commands/load_balancer_monitors.rb +70 -0
  42. data/lib/morpheus/cli/commands/load_balancer_pools.rb +29 -50
  43. data/lib/morpheus/cli/commands/load_balancer_profiles.rb +64 -0
  44. data/lib/morpheus/cli/commands/load_balancer_types.rb +9 -4
  45. data/lib/morpheus/cli/commands/load_balancer_virtual_servers.rb +69 -58
  46. data/lib/morpheus/cli/commands/load_balancers.rb +109 -6
  47. data/lib/morpheus/cli/commands/network_firewalls_command.rb +22 -5
  48. data/lib/morpheus/cli/commands/network_routers_command.rb +96 -45
  49. data/lib/morpheus/cli/commands/network_static_routes_command.rb +451 -0
  50. data/lib/morpheus/cli/commands/network_transport_zones_command.rb +4 -4
  51. data/lib/morpheus/cli/commands/networks_command.rb +2 -2
  52. data/lib/morpheus/cli/commands/open_command.rb +30 -0
  53. data/lib/morpheus/cli/commands/options.rb +98 -0
  54. data/lib/morpheus/cli/commands/ping.rb +3 -5
  55. data/lib/morpheus/cli/commands/policies_command.rb +2 -2
  56. data/lib/morpheus/cli/commands/prices_command.rb +7 -7
  57. data/lib/morpheus/cli/commands/provisioning_settings_command.rb +1 -0
  58. data/lib/morpheus/cli/commands/remote.rb +20 -12
  59. data/lib/morpheus/cli/commands/roles.rb +1 -1
  60. data/lib/morpheus/cli/commands/security_groups.rb +2 -2
  61. data/lib/morpheus/cli/commands/service_plans_command.rb +1 -1
  62. data/lib/morpheus/cli/commands/setup.rb +1 -1
  63. data/lib/morpheus/cli/commands/shell.rb +2 -2
  64. data/lib/morpheus/cli/commands/snapshots.rb +139 -0
  65. data/lib/morpheus/cli/commands/storage_server_types.rb +50 -0
  66. data/lib/morpheus/cli/commands/storage_servers.rb +122 -0
  67. data/lib/morpheus/cli/commands/storage_volume_types.rb +50 -0
  68. data/lib/morpheus/cli/commands/storage_volumes.rb +103 -0
  69. data/lib/morpheus/cli/commands/tasks.rb +5 -5
  70. data/lib/morpheus/cli/commands/tenants_command.rb +1 -1
  71. data/lib/morpheus/cli/commands/user_groups_command.rb +1 -1
  72. data/lib/morpheus/cli/commands/user_settings_command.rb +3 -2
  73. data/lib/morpheus/cli/commands/user_sources_command.rb +1 -1
  74. data/lib/morpheus/cli/commands/users.rb +28 -28
  75. data/lib/morpheus/cli/commands/view.rb +102 -0
  76. data/lib/morpheus/cli/commands/virtual_images.rb +4 -1
  77. data/lib/morpheus/cli/mixins/accounts_helper.rb +5 -5
  78. data/lib/morpheus/cli/mixins/load_balancers_helper.rb +24 -4
  79. data/lib/morpheus/cli/mixins/print_helper.rb +50 -18
  80. data/lib/morpheus/cli/mixins/processes_helper.rb +1 -2
  81. data/lib/morpheus/cli/mixins/provisioning_helper.rb +96 -6
  82. data/lib/morpheus/cli/mixins/rest_command.rb +148 -74
  83. data/lib/morpheus/cli/mixins/secondary_rest_command.rb +174 -82
  84. data/lib/morpheus/cli/mixins/storage_servers_helper.rb +156 -0
  85. data/lib/morpheus/cli/mixins/storage_volumes_helper.rb +119 -0
  86. data/lib/morpheus/cli/option_types.rb +95 -28
  87. data/lib/morpheus/cli/version.rb +1 -1
  88. data/lib/morpheus/cli.rb +1 -0
  89. data/lib/morpheus/ext/string.rb +29 -6
  90. data/lib/morpheus/routes.rb +238 -0
  91. data/lib/morpheus/util.rb +6 -1
  92. metadata +26 -2
@@ -0,0 +1,188 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::Audit
4
+ include Morpheus::Cli::CliCommand
5
+ include Morpheus::Cli::LogsHelper
6
+ include Morpheus::Cli::RestCommand
7
+ include Morpheus::Cli::OptionSourceHelper
8
+
9
+ set_command_description "View audit log records."
10
+ set_command_name :'audit'
11
+ register_subcommands :list, :get
12
+
13
+ # audit is not published yet
14
+ set_command_hidden
15
+
16
+ # RestCommand settings
17
+
18
+ # interfaces
19
+ register_interfaces :audit
20
+ set_rest_interface_name :audit
21
+
22
+ # resource name is "Audit Log"
23
+ set_rest_name :audit_log
24
+
25
+ # display argument as [id] instead of [audit log]
26
+ set_rest_has_name false
27
+ set_rest_arg "id"
28
+
29
+ # def connect(opts)
30
+ # @api_client = establish_remote_appliance_connection(opts)
31
+ # @audit_interface = @api_client.audit # @api_client.rest("audit")
32
+ # end
33
+
34
+ # def handle(args)
35
+ # handle_subcommand(args)
36
+ # end
37
+
38
+ def list(args)
39
+ options = {}
40
+ params = {}
41
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
42
+ opts.banner = subcommand_usage("[search]")
43
+ opts.on('--user USER', String, "Filter by User Username or ID") do |val|
44
+ params['user'] = params['user'] ? [params['user'], val].flatten : [val]
45
+ end
46
+ opts.on('--level VALUE', String, "Log Level. DEBUG|INFO|WARN|ERROR") do |val|
47
+ params['level'] = params['level'] ? [params['level'], val].flatten : [val]
48
+ end
49
+ opts.on('--start TIMESTAMP','--start TIMESTAMP', "Start date timestamp in standard iso8601 format.") do |val|
50
+ params['startDate'] = val # parse_time(val).utc.iso8601
51
+ end
52
+ opts.on('--end TIMESTAMP','--end TIMESTAMP', "End date timestamp in standard iso8601 format.") do |val|
53
+ params['endDate'] = val # parse_time(val).utc.iso8601
54
+ end
55
+ build_standard_list_options(opts, options)
56
+ opts.footer = "List audit logs records."
57
+ end
58
+ optparse.parse!(args)
59
+ if args.count > 0
60
+ options[:phrase] = args.join(" ")
61
+ end
62
+ connect(options)
63
+ params.merge!(parse_list_options(options))
64
+ # parse --user id,name
65
+ if params['user']
66
+ user_ids = parse_user_id_list(params['user'])
67
+ return 1 if user_ids.nil?
68
+ params['user'] = user_ids
69
+ end
70
+ # api works with level=INFO|WARN
71
+ if params['level']
72
+ params['level'] = [params['level']].flatten.collect {|it| it.to_s.upcase }.join('|')
73
+ end
74
+ # could find_by_name_or_id for params['servers'] and params['containers']
75
+ @audit_interface.setopts(options)
76
+ if options[:dry_run]
77
+ print_dry_run @audit_interface.dry.list(params)
78
+ return
79
+ end
80
+ json_response = @audit_interface.list(params)
81
+
82
+ render_response(json_response, options, rest_list_key) do
83
+ records = json_response[rest_list_key]
84
+ print_h1 "Morpheus Audit Log", parse_list_subtitles(options), options
85
+ if records.nil? || records.empty?
86
+ print cyan,"No #{rest_label_plural.downcase} found.",reset,"\n"
87
+ else
88
+ print as_pretty_table(records, rest_list_column_definitions(options).upcase_keys!, options)
89
+ print_results_pagination(json_response) if json_response['meta']
90
+ end
91
+ print reset,"\n"
92
+ end
93
+ return 0, nil
94
+ end
95
+
96
+ protected
97
+
98
+ # custom rendering to print Message below description list
99
+ def render_response_for_get(json_response, options)
100
+ render_response(json_response, options, rest_object_key) do
101
+ record = json_response[rest_object_key]
102
+ print_h1 rest_label, [], options
103
+ print cyan
104
+ print_description_list(rest_column_definitions(options), record, options)
105
+ # show log message settings...
106
+ print_h2 "Message", options
107
+ print cyan
108
+ puts record['message']
109
+ print reset,"\n"
110
+ end
111
+ end
112
+
113
+ def audit_log_object_key
114
+ "auditLog"
115
+ end
116
+
117
+ def audit_log_list_key
118
+ "auditLogs"
119
+ end
120
+
121
+ def audit_log_list_column_definitions(options={})
122
+ {
123
+ "ID" => 'id',
124
+ "Level" => lambda {|it| format_log_level(it['level']) },
125
+ "Message" => {display_method:'message', max_width: (options[:wrap] ? nil : 75)},
126
+ "Event Type" => 'eventType',
127
+ "Object" => lambda {|it| "#{it['objectClass']} #{it['objectId']}".strip },
128
+ # "Object Type" => 'objectClass',
129
+ # "Object ID" => 'objectId',
130
+ "User" => lambda {|it|
131
+ if it['actualUser'] && it['user'] && it['actualUser']['username'] != it['user']['username']
132
+ it['user']['username'] + '(' + it['actualUser']['username'].to_s + ')'
133
+ elsif it['user']
134
+ it['user']['username']
135
+ else
136
+ # system or deleted user maybe?
137
+ end
138
+ },
139
+ # "Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
140
+ "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
141
+ }
142
+ end
143
+
144
+ def audit_log_column_definitions(options={})
145
+ {
146
+ "ID" => 'id',
147
+ "Level" => lambda {|it| format_log_level(it['level']) },
148
+ #"Message" => 'message',
149
+ "Event Type" => 'eventType',
150
+ "Object Type" => 'objectClass',
151
+ "Object ID" => 'objectId',
152
+ "User" => lambda {|it|
153
+ if it['actualUser'] && it['user'] && it['actualUser']['username'] != it['user']['username']
154
+ it['user']['username'] + '(' + it['actualUser']['username'].to_s + ')'
155
+ elsif it['user']
156
+ it['user']['username']
157
+ else
158
+ # system or deleted user maybe?
159
+ end
160
+ },
161
+ # "Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
162
+ "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
163
+ }
164
+ end
165
+
166
+ def find_audit_log_by_name_or_id(val)
167
+ return find_audit_log_by_id(val)
168
+ end
169
+
170
+ def find_audit_log_by_id(id)
171
+ begin
172
+ json_response = @audit_interface.get(id)
173
+ return json_response[audit_log_object_key]
174
+ rescue RestClient::Exception => e
175
+ if e.response && e.response.code == 404
176
+ print_red_alert "Audit Log not found by id #{id}"
177
+ return nil
178
+ else
179
+ raise e
180
+ end
181
+ end
182
+ end
183
+
184
+ def find_audit_log_by_name(name)
185
+ raise_command_error "finding audit log by name not supported"
186
+ end
187
+
188
+ end
@@ -36,7 +36,7 @@ class Morpheus::Cli::BlueprintsCommand
36
36
  @options_interface = @api_client.options
37
37
  @active_group_id = Morpheus::Cli::Groups.active_groups[@appliance_name]
38
38
  @clouds_interface = @api_client.clouds
39
- @users_interface = @api_client.users
39
+ @account_users_interface = @api_client.account_users
40
40
  @library_layouts_interface = @api_client.library_layouts
41
41
  end
42
42
 
@@ -13,6 +13,7 @@ class Morpheus::Cli::CatalogItemTypesCommand
13
13
  set_command_description "Self Service: View and manage catalog item types"
14
14
 
15
15
  register_subcommands :list, :get, :add, :update, :remove
16
+ register_subcommands({:'update-logo' => :update_logo})
16
17
 
17
18
  def connect(opts)
18
19
  @api_client = establish_remote_appliance_connection(opts)
@@ -222,6 +223,7 @@ EOT
222
223
  def add(args)
223
224
  options = {}
224
225
  params = {}
226
+ logo_file = nil
225
227
  optparse = Morpheus::Cli::OptionParser.new do |opts|
226
228
  opts.banner = subcommand_usage("[name] [options]")
227
229
  # opts.on('-t', '--type [instance|blueprint|workflow]', "Item Type, default is instance.") do |val|
@@ -229,6 +231,19 @@ EOT
229
231
  # options[:options]['type'] = val.to_s.downcase
230
232
  # end
231
233
  build_option_type_options(opts, options, add_catalog_item_type_option_types)
234
+ opts.on('--logo FILE', String, "Upload a custom logo icon") do |val|
235
+ filename = val
236
+ logo_file = nil
237
+ if filename == 'null'
238
+ filename = 'null' # clear it
239
+ else
240
+ filename = File.expand_path(filename)
241
+ if !File.exists?(filename)
242
+ raise_command_error "File not found: #{filename}"
243
+ end
244
+ logo_file = File.new(filename, 'rb')
245
+ end
246
+ end
232
247
  opts.on('--config-file FILE', String, "Config from a local JSON or YAML file") do |val|
233
248
  options[:config_file] = val.to_s
234
249
  file_content = nil
@@ -330,6 +345,16 @@ EOT
330
345
  return 0, nil
331
346
  end
332
347
  json_response = @catalog_item_types_interface.create(payload)
348
+ if json_response['success']
349
+ if logo_file
350
+ begin
351
+ @catalog_item_types_interface.update_logo(json_response['catalogItemType']['id'], logo_file)
352
+ rescue RestClient::Exception => e
353
+ print_red_alert "Failed to save logo!"
354
+ print_rest_exception(e, options)
355
+ end
356
+ end
357
+ end
333
358
  catalog_item_type = json_response[catalog_item_type_object_key]
334
359
  render_response(json_response, options, catalog_item_type_object_key) do
335
360
  print_green_success "Added catalog item type #{catalog_item_type['name']}"
@@ -342,9 +367,23 @@ EOT
342
367
  options = {}
343
368
  params = {}
344
369
  payload = {}
370
+ logo_file = nil
345
371
  optparse = Morpheus::Cli::OptionParser.new do |opts|
346
372
  opts.banner = subcommand_usage("[type] [options]")
347
373
  build_option_type_options(opts, options, update_catalog_item_type_option_types)
374
+ opts.on('--logo FILE', String, "Upload a custom logo icon") do |val|
375
+ filename = val
376
+ logo_file = nil
377
+ if filename == 'null'
378
+ filename = 'null' # clear it
379
+ else
380
+ filename = File.expand_path(filename)
381
+ if !File.exists?(filename)
382
+ raise_command_error "File not found: #{filename}"
383
+ end
384
+ logo_file = File.new(filename, 'rb')
385
+ end
386
+ end
348
387
  opts.on('--config-file FILE', String, "Config from a local JSON or YAML file") do |val|
349
388
  options[:config_file] = val.to_s
350
389
  file_content = nil
@@ -445,6 +484,16 @@ EOT
445
484
  return
446
485
  end
447
486
  json_response = @catalog_item_types_interface.update(catalog_item_type['id'], payload)
487
+ if json_response['success']
488
+ if logo_file
489
+ begin
490
+ @catalog_item_types_interface.update_logo(json_response['catalogItemType']['id'], logo_file)
491
+ rescue RestClient::Exception => e
492
+ print_red_alert "Failed to save logo!"
493
+ print_rest_exception(e, options)
494
+ end
495
+ end
496
+ end
448
497
  catalog_item_type = json_response[catalog_item_type_object_key]
449
498
  render_response(json_response, options, catalog_item_type_object_key) do
450
499
  print_green_success "Updated catalog item type #{catalog_item_type['name']}"
@@ -453,6 +502,45 @@ EOT
453
502
  return 0, nil
454
503
  end
455
504
 
505
+ def update_logo(args)
506
+ options = {}
507
+ params = {}
508
+ filename = nil
509
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
510
+ opts.banner = subcommand_usage("[type] [file]")
511
+ build_common_options(opts, options, [:json, :dry_run, :remote])
512
+ opts.footer = "Update the logo for a catalog item type." + "\n" +
513
+ "[type] is required. This is the name or id of a catalog item type." + "\n" +
514
+ "[file] is required. This is the path of the logo file"
515
+ end
516
+ optparse.parse!(args)
517
+ verify_args!(args:args, optparse:optparse, count:2)
518
+ connect(options)
519
+ catalog_item_type = find_catalog_item_type_by_name_or_id(args[0])
520
+ return 1 if catalog_item_type.nil?
521
+ filename = args[1]
522
+ logo_file = nil
523
+ if filename == 'null'
524
+ filename = 'null' # clear it
525
+ else
526
+ filename = File.expand_path(filename)
527
+ if !File.exists?(filename)
528
+ raise_command_error "File not found: #{filename}"
529
+ end
530
+ logo_file = File.new(filename, 'rb')
531
+ end
532
+ @catalog_item_types_interface.setopts(options)
533
+ if options[:dry_run]
534
+ print_dry_run @catalog_item_types_interface.dry.update_logo(catalog_item_type['id'], logo_file)
535
+ return
536
+ end
537
+ json_response = @catalog_item_types_interface.update_logo(catalog_item_type['id'], logo_file)
538
+ render_response(json_response, options, catalog_item_type_object_key) do
539
+ print_green_success "Updated catalog item type #{catalog_item_type['name']} logo"
540
+ return _get(catalog_item_type["id"], {}, options)
541
+ end
542
+ end
543
+
456
544
  def remove(args)
457
545
  options = {}
458
546
  params = {}
@@ -9,7 +9,7 @@ class Morpheus::Cli::ChangePasswordCommand
9
9
  def connect(opts)
10
10
  @api_client = establish_remote_appliance_connection(opts)
11
11
  @whoami_interface = @api_client.whoami
12
- @users_interface = @api_client.users
12
+ @account_users_interface = @api_client.account_users
13
13
  @accounts_interface = @api_client.accounts
14
14
  @roles_interface = @api_client.roles
15
15
  end
@@ -105,9 +105,9 @@ class Morpheus::Cli::ChangePasswordCommand
105
105
  'password' => new_password
106
106
  }
107
107
  }
108
- @users_interface.setopts(options)
108
+ @account_users_interface.setopts(options)
109
109
  if options[:dry_run]
110
- print_dry_run @users_interface.dry.update(account_id, user['id'], payload)
110
+ print_dry_run @account_users_interface.dry.update(account_id, user['id'], payload)
111
111
  return 0
112
112
  end
113
113
 
@@ -117,7 +117,7 @@ class Morpheus::Cli::ChangePasswordCommand
117
117
  end
118
118
  end
119
119
 
120
- json_response = @users_interface.update(account_id, user['id'], payload)
120
+ json_response = @account_users_interface.update(account_id, user['id'], payload)
121
121
  if options[:json]
122
122
  puts as_json(json_response)
123
123
  elsif !options[:quiet]
@@ -458,6 +458,10 @@ class Morpheus::Cli::Clusters
458
458
 
459
459
  cluster_payload['type'] = cluster_type['code'] # {'id' => cluster_type['id']}
460
460
 
461
+ # Group / Site
462
+ group = load_group(cluster_type['code'], options)
463
+ cluster_payload['group'] = {'id' => group['id']}
464
+
461
465
  # Cluster Name
462
466
  if args.empty? && options[:no_prompt]
463
467
  print_red_alert "No cluster name provided"
@@ -514,10 +518,6 @@ class Morpheus::Cli::Clusters
514
518
 
515
519
  server_payload['tags'] = tags if tags
516
520
 
517
- # Group / Site
518
- group = load_group(options)
519
- cluster_payload['group'] = {'id' => group['id']}
520
-
521
521
  # Cloud / Zone
522
522
  cloud_id = nil
523
523
  cloud = options[:cloud] ? find_cloud_by_name_or_id_for_provisioning(group['id'], options[:cloud]) : nil
@@ -526,7 +526,7 @@ class Morpheus::Cli::Clusters
526
526
  cloud = @clouds_interface.get(cloud['id'])['zone']
527
527
  cloud_id = cloud['id']
528
528
  else
529
- available_clouds = get_available_clouds(group['id'])
529
+ available_clouds = get_available_clouds(group['id'], {groupType: cluster_payload['type']})
530
530
 
531
531
  if available_clouds.empty?
532
532
  print_red_alert "Group #{group['name']} has no available clouds"
@@ -547,30 +547,34 @@ class Morpheus::Cli::Clusters
547
547
  available_layouts = layouts_for_dropdown(cloud['id'], cluster_type['id'])
548
548
 
549
549
  if !available_layouts.empty?
550
- if available_layouts.count > 1 && !options[:no_prompt]
551
- layout_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'layout', 'type' => 'select', 'fieldLabel' => 'Layout', 'selectOptions' => available_layouts, 'required' => true, 'description' => 'Select Layout.'}],options[:options],@api_client,{})['layout']
552
- else
553
- layout_id = available_layouts.first['id']
554
- end
550
+ layout_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'layout', 'type' => 'select', 'fieldLabel' => 'Layout', 'selectOptions' => available_layouts, 'required' => true, 'description' => 'Select Layout.'}],options[:options],@api_client,{})['layout']
555
551
  layout = find_layout_by_name_or_id(layout_id)
556
552
  end
557
553
  end
558
554
 
559
- cluster_payload['layout'] = {id: layout['id']}
555
+ cluster_payload['layout'] = {'id' => layout['id']}
560
556
 
561
557
  # Provision Type
562
558
  provision_type = (layout && layout['provisionType'] ? layout['provisionType'] : nil) || get_provision_type_for_zone_type(cloud['zoneType']['id'])
559
+ provision_type = @provision_types_interface.get(provision_type['id'])['provisionType'] if !provision_type.nil?
563
560
 
564
- api_params = {zoneId: cloud['id'], siteId: group['id'], layoutId: layout['id'], groupTypeId: cluster_type['id'], provisionTypeId: provision_type['id']}
561
+ api_params = {zoneId: cloud['id'], siteId: group['id'], layoutId: layout['id'], groupTypeId: cluster_type['id'], provisionType: provision_type['code'], provisionTypeId: provision_type['id']}
565
562
 
566
563
  # Controller type
567
- server_types = @server_types_interface.list({max:1, computeTypeId: cluster_type['controllerTypes'].first['id'], zoneTypeId: cloud['zoneType']['id'], useZoneProvisionTypes: true})['serverTypes']
564
+ server_types = @server_types_interface.list({computeTypeId: cluster_type['controllerTypes'].first['id'], zoneTypeId: cloud['zoneType']['id'], useZoneProvisionTypes: true})['serverTypes'].reject {|it| it['provisionType']['code'] == 'manual'}
568
565
  controller_provision_type = nil
569
566
  resource_pool = nil
570
567
 
571
568
  if !server_types.empty?
572
569
  controller_type = server_types.first
573
- controller_provision_type = controller_type['provisionType'] ? (@provision_types_interface.get(controller_type['provisionType']['id'])['provisionType'] rescue nil) : nil
570
+
571
+ if controller_type['provisionType']
572
+ if provision_type && provision_type['id'] == controller_type['provisionType']['id']
573
+ controller_provision_type = provision_type
574
+ else
575
+ controller_provision_type = @provision_types_interface.get(controller_type['provisionType']['id'])['provisionType'] rescue nil
576
+ end
577
+ end
574
578
 
575
579
  if controller_provision_type && resource_pool = prompt_resource_pool(group, cloud, nil, controller_provision_type, options)
576
580
  server_payload['config']['resourcePoolId'] = resource_pool['id']
@@ -585,21 +589,23 @@ class Morpheus::Cli::Clusters
585
589
  service_plan = prompt_service_plan(api_params, options)
586
590
 
587
591
  if service_plan
588
- server_payload['plan'] = {'id' => service_plan['id'], 'code' => service_plan['code'], 'options' => prompt_service_plan_options(service_plan, options)}
592
+ server_payload['plan'] = {'id' => service_plan['id'], 'code' => service_plan['code'], 'options' => prompt_service_plan_options(service_plan, provision_type, options)}
589
593
  api_params['planId'] = service_plan['id']
590
594
  end
591
595
 
592
596
  # Multi-disk / prompt for volumes
593
- volumes = options[:volumes] || prompt_volumes(service_plan, options.merge({'defaultAddFirstDataVolume': true}), @api_client, api_params)
594
-
595
- if !volumes.empty?
596
- server_payload['volumes'] = volumes
597
+ if provision_type['hasVolumes']
598
+ volumes = options[:volumes] || prompt_volumes(service_plan, options.merge({'defaultAddFirstDataVolume': true}), @api_client, api_params)
599
+ if !volumes.empty?
600
+ server_payload['volumes'] = volumes
601
+ end
597
602
  end
598
603
 
599
604
  # Options / Custom Config
600
605
  option_type_list =
601
- ((controller_type['optionTypes'].reject { |type| !type['enabled'] || type['fieldComponent'] } rescue []) + layout['optionTypes'] +
602
- (cluster_type['optionTypes'].reject { |type| !type['enabled'] || !type['creatable'] || type['fieldComponent'] } rescue [])).sort { |type| type['displayOrder'] }
606
+ ((controller_type.nil? ? [] : controller_type['optionTypes'].reject { |type| !type['enabled'] || type['fieldComponent'] } rescue []) +
607
+ layout['optionTypes'] +
608
+ (cluster_type['optionTypes'].reject { |type| !type['enabled'] || !type['creatable'] || type['fieldComponent'] } rescue []))
603
609
 
604
610
  # KLUDGE: google zone required for network selection
605
611
  if option_type = option_type_list.find {|type| type['code'] == 'computeServerType.googleLinux.googleZoneId'}
@@ -621,11 +627,17 @@ class Morpheus::Cli::Clusters
621
627
  # Visibility
622
628
  server_payload['visibility'] = options[:visibility] || (Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'visibility', 'fieldLabel' => 'Visibility', 'type' => 'select', 'defaultValue' => 'private', 'required' => true, 'selectOptions' => [{'name' => 'Private', 'value' => 'private'},{'name' => 'Public', 'value' => 'public'}]}], options[:options], @api_client, {})['visibility'])
623
629
 
624
- server_payload.deep_merge!(Morpheus::Cli::OptionTypes.prompt(option_type_list, options[:options], @api_client, api_params, options[:no_prompt], true))
630
+ # Layout template options
631
+ cluster_payload.deep_merge!(Morpheus::Cli::OptionTypes.prompt(load_layout_options(cluster_payload), options[:options], @api_client, api_params, options[:no_prompt], true))
632
+
633
+ # Server options
634
+ server_payload.deep_merge!(Morpheus::Cli::OptionTypes.prompt(option_type_list, options[:options].deep_merge({:context_map => {'domain' => ''}}), @api_client, api_params, options[:no_prompt], true))
625
635
 
626
636
  # Worker count
627
- default_node_count = layout['computeServers'] ? (layout['computeServers'].find {|it| it['nodeType'] == 'worker'} || {'nodeCount' => 3})['nodeCount'] : 3
628
- server_payload['config']['nodeCount'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => "config.nodeCount", 'type' => 'number', 'fieldLabel' => "#{cluster_type['code'].include?('docker') ? 'Host' : 'Worker'} Count", 'required' => true, 'defaultValue' => default_node_count > 0 ? default_node_count : 3}], options[:options], @api_client, api_params, options[:no_prompt])['config']['nodeCount']
637
+ if !['manual', 'external'].include?(provision_type['code'])
638
+ default_node_count = layout['computeServers'] ? (layout['computeServers'].find {|it| it['nodeType'] == 'worker'} || {'nodeCount' => 3})['nodeCount'] : 3
639
+ server_payload['config']['nodeCount'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => "config.nodeCount", 'type' => 'number', 'fieldLabel' => "#{['docker-cluster', 'kvm-cluster'].include?(cluster_type['code']) ? 'Host' : 'Worker'} Count", 'required' => true, 'defaultValue' => default_node_count > 0 ? default_node_count : 3}], options[:options], @api_client, api_params, options[:no_prompt])['config']['nodeCount']
640
+ end
629
641
 
630
642
  # Create User
631
643
  if !options[:createUser].nil?
@@ -653,10 +665,12 @@ class Morpheus::Cli::Clusters
653
665
  server_payload['hostname'] = options[:hostname] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'hostname', 'fieldLabel' => 'Hostname', 'type' => 'text', 'required' => true, 'description' => 'Hostname', 'defaultValue' => resourceName}], options[:options], @api_client, api_params)['hostname']
654
666
 
655
667
  # Workflow / Automation
656
- task_set_id = options[:taskSetId] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'taskSet', 'fieldLabel' => 'Workflow', 'type' => 'select', 'required' => false, 'optionSource' => 'taskSets'}], options[:options], @api_client, api_params.merge({'phase' => 'postProvision'}))['taskSet']
668
+ if provision_type['code'] != 'manual' && controller_type && controller_type['hasAutomation']
669
+ task_set_id = options[:taskSetId] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'taskSet', 'fieldLabel' => 'Workflow', 'type' => 'select', 'required' => false, 'optionSource' => 'taskSets'}], options[:options], @api_client, api_params.merge({'phase' => 'postProvision'}))['taskSet']
657
670
 
658
- if !task_set_id.nil?
659
- server_payload['taskSet'] = {'id' => task_set_id}
671
+ if !task_set_id.nil?
672
+ server_payload['taskSet'] = {'id' => task_set_id}
673
+ end
660
674
  end
661
675
 
662
676
  cluster_payload['server'] = server_payload
@@ -1168,7 +1182,7 @@ class Morpheus::Cli::Clusters
1168
1182
  service_plan = prompt_service_plan({zoneId: cloud_id, siteId: cluster['site']['id'], provisionTypeId: server_type['provisionType']['id'], groupTypeId: cluster_type['id'], }, options)
1169
1183
 
1170
1184
  if service_plan
1171
- server_payload['plan'] = {'code' => service_plan['code'], 'options' => prompt_service_plan_options(service_plan, options)}
1185
+ server_payload['plan'] = {'code' => service_plan['code'], 'options' => prompt_service_plan_options(service_plan, nil, options)}
1172
1186
  end
1173
1187
 
1174
1188
  if resource_pool = prompt_resource_pool(cluster, cloud, service_plan, server_type['provisionType'], options)
@@ -3665,7 +3679,7 @@ class Morpheus::Cli::Clusters
3665
3679
  @clouds_interface.cloud_type(zone_type_id)['zoneType']['provisionTypes'].first rescue nil
3666
3680
  end
3667
3681
 
3668
- def load_group(options)
3682
+ def load_group(group_type, options)
3669
3683
  # Group / Site
3670
3684
  group_id = nil
3671
3685
  group = options[:group] ? find_group_by_name_or_id_for_provisioning(options[:group]) : nil
@@ -3676,15 +3690,13 @@ class Morpheus::Cli::Clusters
3676
3690
  if @active_group_id
3677
3691
  group_id = @active_group_id
3678
3692
  else
3679
- available_groups = get_available_groups
3693
+ available_groups = get_available_groups({groupType: group_type})
3680
3694
 
3681
3695
  if available_groups.empty?
3682
3696
  print_red_alert "No available groups"
3683
3697
  exit 1
3684
- elsif available_groups.count > 1 && !options[:no_prompt]
3698
+ else available_groups.count > 1 && !options[:no_prompt]
3685
3699
  group_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'type' => 'select', 'fieldLabel' => 'Group', 'selectOptions' => available_groups, 'required' => true, 'description' => 'Select Group.'}],options[:options],@api_client,{})['group']
3686
- else
3687
- group_id = available_groups.first['id']
3688
3700
  end
3689
3701
  end
3690
3702
  end
@@ -3711,34 +3723,37 @@ class Morpheus::Cli::Clusters
3711
3723
  service_plan
3712
3724
  end
3713
3725
 
3714
- def prompt_service_plan_options(service_plan, options)
3726
+ def prompt_service_plan_options(service_plan, provision_type, options)
3715
3727
  plan_options = {}
3716
-
3717
- # custom max memory
3718
- if service_plan['customMaxMemory']
3719
- if !options[:maxMemory]
3720
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'maxMemory', 'type' => 'number', 'fieldLabel' => 'Max Memory (MB)', 'required' => false, 'description' => 'This will override any memory requirement set on the virtual image', 'defaultValue' => service_plan['maxMemory'] ? service_plan['maxMemory'] / (1024 * 1024) : 10 }], options[:options])
3721
- plan_options['maxMemory'] = v_prompt['maxMemory'] * 1024 * 1024 if v_prompt['maxMemory']
3722
- else
3723
- plan_options['maxMemory'] = options[:maxMemory]
3728
+ hide_custom_options = provision_type && provision_type['code'] == 'manual'
3729
+
3730
+ if !hide_custom_options
3731
+ # custom max memory
3732
+ if service_plan['customMaxMemory']
3733
+ if !options[:maxMemory]
3734
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'maxMemory', 'type' => 'number', 'fieldLabel' => 'Max Memory (MB)', 'required' => false, 'description' => 'This will override any memory requirement set on the virtual image', 'defaultValue' => service_plan['maxMemory'] ? service_plan['maxMemory'] / (1024 * 1024) : 10 }], options[:options])
3735
+ plan_options['maxMemory'] = v_prompt['maxMemory'] * 1024 * 1024 if v_prompt['maxMemory']
3736
+ else
3737
+ plan_options['maxMemory'] = options[:maxMemory]
3738
+ end
3724
3739
  end
3725
- end
3726
3740
 
3727
- # custom cores: max cpu, max cores, cores per socket
3728
- if service_plan['customCores']
3729
- if options[:cpuCount].empty?
3730
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cpuCount', 'type' => 'number', 'fieldLabel' => 'CPU Count', 'required' => false, 'description' => 'Set CPU Count', 'defaultValue' => service_plan['maxCpu'] ? service_plan['maxCpu'] : 1 }], options[:options])
3731
- plan_options['cpuCount'] = v_prompt['cpuCount'] if v_prompt['cpuCount']
3732
- else
3733
- plan_options['cpuCount']
3734
- end
3735
- if options[:coreCount].empty?
3736
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'coreCount', 'type' => 'number', 'fieldLabel' => 'Core Count', 'required' => false, 'description' => 'Set Core Count', 'defaultValue' => service_plan['maxCores'] ? service_plan['maxCores'] : 1 }], options[:options])
3737
- plan_options['coreCount'] = v_prompt['coreCount'] if v_prompt['coreCount']
3738
- end
3739
- if options[:coresPerSocket].empty? && service_plan['coresPerSocket']
3740
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'coresPerSocket', 'type' => 'number', 'fieldLabel' => 'Cores Per Socket', 'required' => false, 'description' => 'Set Core Per Socket', 'defaultValue' => service_plan['coresPerSocket']}], options[:options])
3741
- plan_options['coresPerSocket'] = v_prompt['coresPerSocket'] if v_prompt['coresPerSocket']
3741
+ # custom cores: max cpu, max cores, cores per socket
3742
+ if service_plan['customCores']
3743
+ if options[:cpuCount].empty?
3744
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cpuCount', 'type' => 'number', 'fieldLabel' => 'CPU Count', 'required' => false, 'description' => 'Set CPU Count', 'defaultValue' => service_plan['maxCpu'] ? service_plan['maxCpu'] : 1 }], options[:options])
3745
+ plan_options['cpuCount'] = v_prompt['cpuCount'] if v_prompt['cpuCount']
3746
+ else
3747
+ plan_options['cpuCount']
3748
+ end
3749
+ if options[:coreCount].empty?
3750
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'coreCount', 'type' => 'number', 'fieldLabel' => 'Core Count', 'required' => false, 'description' => 'Set Core Count', 'defaultValue' => service_plan['maxCores'] ? service_plan['maxCores'] : 1 }], options[:options])
3751
+ plan_options['coreCount'] = v_prompt['coreCount'] if v_prompt['coreCount']
3752
+ end
3753
+ if options[:coresPerSocket].empty? && service_plan['coresPerSocket']
3754
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'coresPerSocket', 'type' => 'number', 'fieldLabel' => 'Cores Per Socket', 'required' => false, 'description' => 'Set Core Per Socket', 'defaultValue' => service_plan['coresPerSocket']}], options[:options])
3755
+ plan_options['coresPerSocket'] = v_prompt['coresPerSocket'] if v_prompt['coresPerSocket']
3756
+ end
3742
3757
  end
3743
3758
  end
3744
3759
  plan_options
@@ -3926,4 +3941,27 @@ class Morpheus::Cli::Clusters
3926
3941
  ]
3927
3942
  end
3928
3943
 
3944
+ def load_layout_options(cluster)
3945
+ (@api_client.options.options_for_source('computeTypeLayoutParameters', {layoutId: cluster['layout']['id']})['data'] || []).collect do |it|
3946
+ it['fieldName'] = it['name']
3947
+ it['fieldLabel'] = it['displayName']
3948
+ it['fieldContext'] = 'config.templateParameter'
3949
+ if it['type'] == 'ServicePlan'
3950
+ it['optionSource'] = 'servicePlans'
3951
+ it['type'] = 'select'
3952
+ it['params'] = {:provisionType => '', :zoneId => '', :siteId => '', :resourcePoolId => ''}
3953
+ it['params'][:provisionType] = 'azure' if it['azureServicePlanType']
3954
+ elsif it['type'] == 'Subnet'
3955
+ it['optionSource'] = 'networks'
3956
+ it['type'] = 'select'
3957
+ it['params'] = {:siteId => '', :instanceId => '', :serverId => '', :zonePoolId => '', :zoneRegionId => '', :zoneId => '', :provisionType => ''}
3958
+ if cluster['type'] == 'aks-cluster'
3959
+ it['params'][:provisionType] = 'azure'
3960
+ end
3961
+ else
3962
+ it['type'] = 'text'
3963
+ end
3964
+ it
3965
+ end
3966
+ end
3929
3967
  end