morpheus-cli 4.1.8 → 4.1.9

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +24 -0
  4. data/lib/morpheus/api/{old_cypher_interface.rb → budgets_interface.rb} +10 -11
  5. data/lib/morpheus/api/cloud_datastores_interface.rb +7 -0
  6. data/lib/morpheus/api/cloud_resource_pools_interface.rb +2 -2
  7. data/lib/morpheus/api/cypher_interface.rb +18 -12
  8. data/lib/morpheus/api/health_interface.rb +72 -0
  9. data/lib/morpheus/api/instances_interface.rb +1 -1
  10. data/lib/morpheus/api/library_instance_types_interface.rb +7 -0
  11. data/lib/morpheus/api/log_settings_interface.rb +6 -0
  12. data/lib/morpheus/api/network_security_servers_interface.rb +30 -0
  13. data/lib/morpheus/api/price_sets_interface.rb +42 -0
  14. data/lib/morpheus/api/prices_interface.rb +68 -0
  15. data/lib/morpheus/api/provisioning_settings_interface.rb +29 -0
  16. data/lib/morpheus/api/servers_interface.rb +1 -1
  17. data/lib/morpheus/api/service_plans_interface.rb +34 -11
  18. data/lib/morpheus/api/task_sets_interface.rb +8 -0
  19. data/lib/morpheus/api/tasks_interface.rb +8 -0
  20. data/lib/morpheus/cli.rb +6 -3
  21. data/lib/morpheus/cli/appliance_settings_command.rb +13 -5
  22. data/lib/morpheus/cli/approvals_command.rb +1 -1
  23. data/lib/morpheus/cli/apps.rb +88 -28
  24. data/lib/morpheus/cli/backup_settings_command.rb +1 -1
  25. data/lib/morpheus/cli/blueprints_command.rb +2 -0
  26. data/lib/morpheus/cli/budgets_command.rb +672 -0
  27. data/lib/morpheus/cli/cli_command.rb +13 -2
  28. data/lib/morpheus/cli/cli_registry.rb +1 -0
  29. data/lib/morpheus/cli/clusters.rb +40 -274
  30. data/lib/morpheus/cli/commands/standard/benchmark_command.rb +114 -66
  31. data/lib/morpheus/cli/commands/standard/coloring_command.rb +12 -0
  32. data/lib/morpheus/cli/commands/standard/curl_command.rb +31 -6
  33. data/lib/morpheus/cli/commands/standard/echo_command.rb +8 -3
  34. data/lib/morpheus/cli/commands/standard/set_prompt_command.rb +1 -1
  35. data/lib/morpheus/cli/containers_command.rb +37 -24
  36. data/lib/morpheus/cli/cypher_command.rb +191 -150
  37. data/lib/morpheus/cli/health_command.rb +903 -0
  38. data/lib/morpheus/cli/hosts.rb +43 -32
  39. data/lib/morpheus/cli/instances.rb +119 -68
  40. data/lib/morpheus/cli/jobs_command.rb +1 -1
  41. data/lib/morpheus/cli/library_instance_types_command.rb +61 -11
  42. data/lib/morpheus/cli/library_option_types_command.rb +2 -2
  43. data/lib/morpheus/cli/log_settings_command.rb +46 -3
  44. data/lib/morpheus/cli/logs_command.rb +24 -17
  45. data/lib/morpheus/cli/mixins/accounts_helper.rb +2 -0
  46. data/lib/morpheus/cli/mixins/logs_helper.rb +73 -19
  47. data/lib/morpheus/cli/mixins/print_helper.rb +29 -1
  48. data/lib/morpheus/cli/mixins/provisioning_helper.rb +554 -96
  49. data/lib/morpheus/cli/mixins/whoami_helper.rb +13 -1
  50. data/lib/morpheus/cli/networks_command.rb +3 -0
  51. data/lib/morpheus/cli/option_types.rb +83 -53
  52. data/lib/morpheus/cli/price_sets_command.rb +543 -0
  53. data/lib/morpheus/cli/prices_command.rb +669 -0
  54. data/lib/morpheus/cli/processes_command.rb +0 -2
  55. data/lib/morpheus/cli/provisioning_settings_command.rb +237 -0
  56. data/lib/morpheus/cli/remote.rb +9 -4
  57. data/lib/morpheus/cli/reports_command.rb +10 -4
  58. data/lib/morpheus/cli/roles.rb +93 -38
  59. data/lib/morpheus/cli/security_groups.rb +10 -0
  60. data/lib/morpheus/cli/service_plans_command.rb +736 -0
  61. data/lib/morpheus/cli/tasks.rb +220 -8
  62. data/lib/morpheus/cli/tenants_command.rb +3 -16
  63. data/lib/morpheus/cli/users.rb +2 -25
  64. data/lib/morpheus/cli/version.rb +1 -1
  65. data/lib/morpheus/cli/whitelabel_settings_command.rb +18 -18
  66. data/lib/morpheus/cli/whoami.rb +28 -10
  67. data/lib/morpheus/cli/workflows.rb +488 -36
  68. data/lib/morpheus/formatters.rb +22 -0
  69. data/morpheus-cli.gemspec +1 -0
  70. metadata +28 -5
  71. data/lib/morpheus/cli/accounts.rb +0 -335
  72. data/lib/morpheus/cli/old_cypher_command.rb +0 -412
@@ -23,6 +23,7 @@ class Morpheus::Cli::SecurityGroups
23
23
  @clouds_interface = @api_client.clouds
24
24
  @options_interface = @api_client.options
25
25
  @active_security_group = ::Morpheus::Cli::SecurityGroups.load_security_group_file
26
+ @network_security_servers = @api_client.network_security_servers
26
27
  end
27
28
 
28
29
  def handle(args)
@@ -353,6 +354,15 @@ class Morpheus::Cli::SecurityGroups
353
354
  #v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'zoneId', 'fieldLabel' => 'Scoped Cloud', 'type' => 'select', 'optionSource' => 'cloudsForSecurityGroup', 'required' => false}], options[:options], @api_client)
354
355
  if !v_prompt['zoneId'].to_s.empty? && v_prompt['zoneId'].to_s != 'all' && v_prompt['zoneId'].to_s != '-1'
355
356
  payload['securityGroup']['zoneId'] = v_prompt['zoneId']
357
+
358
+ zone = find_cloud_by_id(payload['securityGroup']['zoneId'])
359
+ if zone['securityServer']
360
+ sec_server = @network_security_servers.get(zone['securityServer']['id'])['networkSecurityServer']
361
+
362
+ if sec_server['type']
363
+ payload['securityGroup'].deep_merge!(Morpheus::Cli::OptionTypes.prompt(sec_server['type']['optionTypes'], options[:options], @api_client, {zoneId: zone['id']}))
364
+ end
365
+ end
356
366
  end
357
367
  rescue => ex
358
368
  print yellow,"Failed to determine the available scoped clouds.",reset,"\n"
@@ -0,0 +1,736 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::ServicePlanCommand
4
+ include Morpheus::Cli::CliCommand
5
+ include Morpheus::Cli::AccountsHelper
6
+ include Morpheus::Cli::ProvisioningHelper
7
+
8
+ set_command_name :'service-plans'
9
+
10
+ register_subcommands :list, :get, :add, :update, :deactivate
11
+ set_default_subcommand :list
12
+
13
+ def connect(opts)
14
+ @api_client = establish_remote_appliance_connection(opts)
15
+ @service_plans_interface = @api_client.service_plans
16
+ @provision_types_interface = @api_client.provision_types
17
+ @options_interface = @api_client.options
18
+ @accounts_interface = @api_client.accounts
19
+ @price_sets_interface = @api_client.price_sets
20
+ end
21
+
22
+ def handle(args)
23
+ handle_subcommand(args)
24
+ end
25
+
26
+ def list(args)
27
+ options = {}
28
+ params = {'includeZones': true}
29
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
30
+ opts.banner = subcommand_usage()
31
+ opts.on('-t', '--provision-type VALUE', String, "Filter by provision type ID or code") do |val|
32
+ options[:provisionType] = val
33
+ end
34
+ opts.on('-i', '--include-inactive [on|off]', String, "Can be used to enable / disable inactive filter. Default is on") do |val|
35
+ params['includeInactive'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
36
+ end
37
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
38
+ opts.footer = "List service plans."
39
+ end
40
+ optparse.parse!(args)
41
+ connect(options)
42
+ if args.count != 0
43
+ raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
44
+ return 1
45
+ end
46
+
47
+ begin
48
+ params.merge!(parse_list_options(options))
49
+
50
+ if !options[:provisionType].nil?
51
+ type = find_provision_type(options[:provisionType])
52
+
53
+ if type.nil?
54
+ print_red_alert "Provision type #{options[:provisionType]} not found"
55
+ exit 1
56
+ end
57
+ params['provisionTypeId'] = type['id']
58
+ end
59
+
60
+ @service_plans_interface.setopts(options)
61
+ if options[:dry_run]
62
+ print_dry_run @service_plans_interface.dry.list(params)
63
+ return
64
+ end
65
+ json_response = @service_plans_interface.list(params)
66
+
67
+ render_result = render_with_format(json_response, options, 'servicePlans')
68
+ return 0 if render_result
69
+
70
+ title = "Morpheus Service Plans"
71
+ subtitles = []
72
+ subtitles += parse_list_subtitles(options)
73
+ print_h1 title, subtitles
74
+
75
+ plans = json_response['servicePlans']
76
+ if plans.empty?
77
+ print yellow,"No service plans found.",reset,"\n"
78
+ else
79
+ rows = plans.collect do |it|
80
+ {
81
+ id: (it['active'] ? cyan : yellow) + it['id'].to_s,
82
+ name: it['name'],
83
+ type: it['provisionType'] ? it['provisionType']['name'] : '',
84
+ active: format_boolean(it['active']),
85
+ cores: it['maxCores'],
86
+ memory: format_bytes(it['maxMemory']),
87
+ clouds: it['zones'] ? truncate_string(it['zones'].collect {|it| it['name']}.join(', '), 30) : '',
88
+ visibility: (it['visibility'] || '').capitalize,
89
+ tenants: it['tenants'] || 'Global',
90
+ price_sets: (it['priceSets'] ? it['priceSets'].count : 0).to_s + cyan
91
+ }
92
+ end
93
+ columns = [
94
+ :id, :name, :type, :active, :cores, :memory, :clouds, :visibility, :tenants, :price_sets
95
+ ]
96
+ columns.delete(:active) if !params['includeInactive']
97
+
98
+ print as_pretty_table(rows, columns, options)
99
+ print_results_pagination(json_response)
100
+ print reset,"\n"
101
+ end
102
+ print reset,"\n"
103
+ return 0
104
+ rescue RestClient::Exception => e
105
+ print_rest_exception(e, options)
106
+ exit 1
107
+ end
108
+ end
109
+
110
+ def get(args)
111
+ options = {}
112
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
113
+ opts.banner = subcommand_usage("[plan]")
114
+ build_common_options(opts, options, [:json, :dry_run, :remote])
115
+ opts.footer = "Get details about a service plan.\n" +
116
+ "[plan] is required. Service plan ID, name or code"
117
+ end
118
+ optparse.parse!(args)
119
+ if args.count != 1
120
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
121
+ end
122
+ connect(options)
123
+ return _get(args[0], options)
124
+ end
125
+
126
+ def _get(plan_id, options = {})
127
+ params = {}
128
+ begin
129
+ @service_plans_interface.setopts(options)
130
+
131
+ if !(plan_id.to_s =~ /\A\d{1,}\Z/)
132
+ plan = find_service_plan(plan_id)
133
+
134
+ if !plan
135
+ print_red_alert "Service plan #{plan_id} not found"
136
+ exit 1
137
+ end
138
+ plan_id = plan['id']
139
+ end
140
+
141
+ if options[:dry_run]
142
+ print_dry_run @service_plans_interface.dry.get(plan_id)
143
+ return
144
+ end
145
+ json_response = @service_plans_interface.get(plan_id)
146
+
147
+ render_result = render_with_format(json_response, options, 'servicePlan')
148
+ return 0 if render_result
149
+
150
+ title = "Morpheus Service Plan"
151
+ subtitles = []
152
+ subtitles += parse_list_subtitles(options)
153
+ print_h1 title, subtitles
154
+
155
+ service_plan = json_response['servicePlan']
156
+ print cyan
157
+ description_cols = {
158
+ "ID" => lambda {|it| it['id']},
159
+ "Name" => lambda {|it| it['name'] || (it['accountIntegration'] ? 'Pending' : 'Not Set')},
160
+ "Active" => lambda {|it| format_boolean(it['active'])},
161
+ "Visibility" => lambda {|it| (it['visibility'] || '').capitalize},
162
+ "Code" => lambda {|it| it['code']},
163
+ "Display Order" => lambda {|it| it['sortOrder']},
164
+ "Provision Type" => lambda {|it| it['provisionType'] ? it['provisionType']['name'] : ''},
165
+ "Storage" => lambda {|it| printable_byte_size(it, it['maxStorage'], 'storageSizeType', 'GB')}
166
+ }
167
+
168
+ provision_type = service_plan['provisionType'] || {}
169
+
170
+ description_cols['Customize Root Volume'] = lambda {|it| format_boolean(it['customMaxStorage'])} if provision_type['rootDiskCustomizable']
171
+ description_cols['Customize Extra Volumes'] = lambda {|it| format_boolean(it['customMaxDataStorage'])} if provision_type['customizeVolume']
172
+ description_cols['Add Volumes'] = lambda {|it| format_boolean(it['addVolumes'])} if provision_type['addVolumes']
173
+ description_cols['Max Disks Allowed'] = lambda {|it| it['maxDisks'] || 0} if provision_type['addVolumes']
174
+ description_cols['Memory'] = lambda {|it| printable_byte_size(it, it['maxMemory'], 'memorySizeType')}
175
+ description_cols['Custom Max Memory'] = lambda {|it| format_boolean(it['customMaxMemory'])}
176
+ description_cols['Core Count'] = lambda {|it| it['maxCores']}
177
+ description_cols['Custom Cores'] = lambda {|it| format_boolean(it['customCores'])}
178
+ description_cols['Cores Per Socket'] = lambda {|it| it['coresPerSocket']} if provision_type['hasConfigurableCpuSockets'] && service_plan['customCores']
179
+
180
+ ranges = (service_plan['config'] ? service_plan['config']['ranges'] : nil) || {}
181
+
182
+ if (ranges['minStorage'] && ranges['minStorage'] != '') || (ranges['maxStorage'] && ranges['maxStorage'] != '')
183
+ description_cols['Custom Storage Range'] = lambda {|it|
184
+ get_range(
185
+ ranges['minStorage'] && ranges['minStorage'] != '' ? "#{ranges['minStorage']} #{(it['config'] && it['config']['storageSizeType'] ? it['config']['storageSizeType'] : 'GB').upcase}" : nil,
186
+ ranges['maxStorage'] && ranges['maxStorage'] != '' ? "#{ranges['maxStorage']} #{(it['config'] && it['config']['storageSizeType'] ? it['config']['storageSizeType'] : 'GB').upcase}" : nil,
187
+ )
188
+ }
189
+ end
190
+ if (ranges['minMemory'] && ranges['minMemory'] != '') || (ranges['maxMemory'] && ranges['maxMemory'] != '')
191
+ description_cols['Custom Memory Range'] = lambda {|it|
192
+ get_range(
193
+ ranges['minMemory'] && ranges['minMemory'] != '' ? printable_byte_size(it, ranges['minMemory'], 'memorySizeType') : nil,
194
+ ranges['maxMemory'] && ranges['maxMemory'] != '' ? printable_byte_size(it, ranges['maxMemory'], 'memorySizeType') : nil
195
+ )
196
+ }
197
+ end
198
+ if (ranges['minCores'] && ranges['minCores'] != '') || (ranges['maxCores'] && ranges['maxCores'] != '')
199
+ description_cols['Custom Cores Range'] = lambda {|it|
200
+ get_range(
201
+ ranges['minCores'] && ranges['minCores'] != '' ? ranges['minCores'] : nil,
202
+ ranges['maxCores'] && ranges['maxCores'] != '' ? ranges['maxCores'] : nil
203
+ )
204
+ }
205
+ end
206
+
207
+ print_description_list(description_cols, service_plan)
208
+
209
+ print_h2 "Price Sets"
210
+ price_sets = service_plan['priceSets']
211
+
212
+ if price_sets && !price_sets.empty?
213
+ rows = price_sets.collect do |it|
214
+ {
215
+ id: it['id'],
216
+ unit: (it['priceUnit'] || '').capitalize,
217
+ name: it['name']
218
+ }
219
+ end
220
+ columns = [
221
+ :id, :unit, :name
222
+ ]
223
+ print as_pretty_table(rows, columns, options)
224
+ else
225
+ print yellow,"No price sets.",reset,"\n"
226
+ end
227
+
228
+ print_permissions(service_plan['permissions'], ['plans', 'groupDefaults'])
229
+ print reset,"\n"
230
+ return 0
231
+ rescue RestClient::Exception => e
232
+ print_rest_exception(e, options)
233
+ exit 1
234
+ end
235
+ end
236
+
237
+ def add(args)
238
+ options = {}
239
+ params = {}
240
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
241
+ opts.banner = subcommand_usage()
242
+ opts.on("--name NAME", String, "Service plan name") do |val|
243
+ params['name'] = val.to_s
244
+ end
245
+ opts.on("--code CODE", String, "Service plan code, unique identifier") do |val|
246
+ params['code'] = val.to_s
247
+ end
248
+ opts.on('-t', '--provision-type [TYPE]', String, "Provision type ID or code") do |val|
249
+ options[:provisionType] = val
250
+ end
251
+ opts.on("--description [TEXT]", String, "Description") do |val|
252
+ params['description'] = val.to_s
253
+ end
254
+ opts.on('--editable [on|off]', String, "Can be used to enable / disable the editability of the service plan. Default is on") do |val|
255
+ params['editable'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
256
+ end
257
+ opts.on('--storage [AMOUNT]', String, "Storage size is required. Assumes GB unless optional modifier specified, ex: 512MB" ) do |val|
258
+ bytes = parse_bytes_param(val, '--storage', 'GB')
259
+ params['maxStorage'] = bytes[:bytes]
260
+ (params['config'] ||= {})['storageSizeType'] = bytes[:unit].downcase
261
+ end
262
+ opts.on('--memory [AMOUNT]', String, "Memory size is required. Assumes MB unless optional modifier specified, ex: 1GB" ) do |val|
263
+ bytes = parse_bytes_param(val, '--memory', 'MB')
264
+ params['maxMemory'] = bytes[:bytes]
265
+ (params['config'] ||= {})['memorySizeType'] = bytes[:unit].downcase
266
+ end
267
+ opts.on('--cores [NUMBER]', Integer, "Core count. Default is 1" ) do |val|
268
+ params['maxCores'] = val.to_i || 1
269
+ end
270
+ opts.on('--disks [NUMBER]', Integer, "Max disks allowed" ) do |val|
271
+ params['maxDisks'] = val.to_i || 1
272
+ end
273
+ opts.on('--custom-cores [on|off]', String, "Can be used to enable / disable customizable cores. Default is on") do |val|
274
+ params['customCores'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
275
+ end
276
+ opts.on('--custom-storage [on|off]', String, "Can be used to enable / disable customizable storage. Default is on") do |val|
277
+ params['customMaxStorage'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
278
+ end
279
+ opts.on('--custom-volumes [on|off]', String, "Can be used to enable / disable customizable extra volumes. Default is on") do |val|
280
+ params['customMaxDataStorage'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
281
+ end
282
+ opts.on('--custom-memory [on|off]', String, "Can be used to enable / disable customizable memory. Default is on") do |val|
283
+ params['customMaxMemory'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
284
+ end
285
+ opts.on('--add-volumes [on|off]', String, "Can be used to enable / disable ability to add volumes. Default is on") do |val|
286
+ params['addVolumes'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
287
+ end
288
+ opts.on('--sort-order NUMBER', Integer, "Sort order") do |val|
289
+ params['sortOrder'] = val.to_i
290
+ end
291
+ opts.on('--price-sets [LIST]', Array, 'Price set(s), comma separated list of price set IDs') do |list|
292
+ params['priceSets'] = list.collect {|it| it.to_s.strip.empty? || !it.to_i ? nil : it.to_s.strip}.compact.uniq.collect {|it| {'id' => it.to_i}}
293
+ end
294
+ opts.on('--min-storage NUMBER', String, "Min storage. Assumes GB unless optional modifier specified, ex: 512MB") do |val|
295
+ # Storage doesn't get converted to bytes
296
+ bytes = parse_bytes_param(val, '--min-storage', 'GB')
297
+ ((params['config'] ||= {})['ranges'] ||= {})['minStorage'] = bytes[:number]
298
+ (params['config'] ||= {})['storageSizeType'] = bytes[:unit].downcase
299
+ end
300
+ opts.on('--max-storage NUMBER', String, "Max storage. Assumes GB unless optional modifier specified, ex: 512MB") do |val|
301
+ # Storage doesn't get converted to bytes
302
+ bytes = parse_bytes_param(val, '--max-storage', 'GB')
303
+ ((params['config'] ||= {})['ranges'] ||= {})['maxStorage'] = bytes[:number]
304
+ (params['config'] ||= {})['storageSizeType'] = bytes[:unit].downcase
305
+ end
306
+ opts.on('--min-memory NUMBER', String, "Min memory. Assumes MB unless optional modifier specified, ex: 1GB") do |val|
307
+ # Memory does get converted to bytes
308
+ bytes = parse_bytes_param(val, '--min-memory', 'MB')
309
+ ((params['config'] ||= {})['ranges'] ||= {})['minMemory'] = bytes[:bytes]
310
+ (params['config'] ||= {})['memorySizeType'] = bytes[:unit].downcase
311
+ end
312
+ opts.on('--max-memory NUMBER', String, "Max memory. Assumes MB unless optional modifier specified, ex: 1GB") do |val|
313
+ # Memory does get converted to bytes
314
+ bytes = parse_bytes_param(val, '--max-memory', 'MB')
315
+ ((params['config'] ||= {})['ranges'] ||= {})['maxMemory'] = bytes[:bytes]
316
+ (params['config'] ||= {})['memorySizeType'] = bytes[:unit].downcase
317
+ end
318
+ opts.on('--min-cores NUMBER', String, "Min cores") do |val|
319
+ ((params['config'] ||= {})['ranges'] ||= {})['minCores'] = val.to_i
320
+ end
321
+ opts.on('--max-cores NUMBER', String, "Max cores") do |val|
322
+ ((params['config'] ||= {})['ranges'] ||= {})['maxCores'] = val.to_i
323
+ end
324
+ add_perms_options(opts, options, ['plans', 'groupDefaults'])
325
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote, :quiet])
326
+ opts.footer = "Create service plan"
327
+ end
328
+ optparse.parse!(args)
329
+ connect(options)
330
+ if args.count != 0
331
+ raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
332
+ return 1
333
+ end
334
+
335
+ begin
336
+ payload = parse_payload(options)
337
+
338
+ if !payload
339
+ # name
340
+ params['name'] ||= Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => 'Service Plan Name', 'required' => true, 'description' => 'Service Plan Name.'}],options[:options],@api_client,{}, options[:no_prompt])['name']
341
+
342
+ # code
343
+ params['code'] ||= Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'code', 'type' => 'text', 'fieldLabel' => 'Service Plan Code', 'required' => true, 'defaultValue' => params['name'].gsub(/[^0-9a-z ]/i, '').gsub(' ', '.').downcase, 'description' => 'Service Plan Code.'}],options[:options],@api_client,{}, options[:no_prompt])['code']
344
+
345
+ # provision type
346
+ options[:provisionType] = options[:provisionType] || (args.count > 1 ? args[1] : nil)
347
+ provision_types = @service_plans_interface.provision_types()['provisionTypes']
348
+
349
+ if options[:provisionType].nil? && !options[:no_prompt]
350
+ provision_type_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'provisionType', 'type' => 'select', 'fieldLabel' => 'Provision Type', 'selectOptions' => provision_types.collect {|it| {'name' => it['name'], 'value' => it['id']}}, 'required' => true, 'description' => 'Select Provision Type.'}],options[:options],@api_client,{}, options[:no_prompt], true)['provisionType']
351
+
352
+ if !provision_type_id.nil?
353
+ provision_type = provision_types.find {|it| it['id'] == provision_type_id}
354
+ end
355
+ else
356
+ provision_type = provision_types.find {|it| it['name'] == options[:provisionType] || it['code'] == options[:provisionType] || it['id'] == options[:provisionType].to_i}
357
+
358
+ if provision_type.nil?
359
+ print_red_alert "Provision type #{options[:provisionType]} not found"
360
+ exit 1
361
+ end
362
+ end
363
+
364
+ params['provisionType'] = {'id' => provision_type['id']} if !provision_type.nil?
365
+
366
+ # storage is required
367
+ if params['maxStorage'].nil?
368
+ if options[:no_prompt]
369
+ print_red_alert "Storage size is required"
370
+ exit 1
371
+ end
372
+ while params['maxStorage'].nil? do
373
+ begin
374
+ bytes = parse_bytes_param(
375
+ Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'storage', 'type' => 'text', 'fieldLabel' => 'Storage (GB) [can use MB modifier]', 'required' => true, 'description' => 'Storage (GB)'}],options[:options],@api_client,{}, options[:no_prompt])['storage'],
376
+ 'storage',
377
+ 'GB'
378
+ )
379
+ params['maxStorage'] = bytes[:bytes]
380
+ # (params['config'] ||= {})['storageSizeType'] = bytes[:unit].downcase
381
+ rescue
382
+ print "Invalid Value... Please try again.\n"
383
+ end
384
+ end
385
+ end
386
+
387
+ # memory is required
388
+ if params['maxMemory'].nil?
389
+ if options[:no_prompt]
390
+ print_red_alert "Memory size is required"
391
+ exit 1
392
+ end
393
+ while params['maxMemory'].nil? do
394
+ begin
395
+ bytes = parse_bytes_param(
396
+ Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'memory', 'type' => 'text', 'fieldLabel' => 'Memory (MB) [can use GB modifier]', 'required' => true, 'description' => 'Memory (MB)'}],options[:options],@api_client,{}, options[:no_prompt])['memory'],
397
+ 'memory',
398
+ 'MB'
399
+ )
400
+ params['maxMemory'] = bytes[:bytes]
401
+ # (params['config'] ||= {})['memorySizeType'] = bytes[:unit].downcase
402
+ rescue
403
+ print "Invalid Value... Please try again.\n"
404
+ end
405
+ end
406
+ end
407
+
408
+ # price sets
409
+ if params['priceSets'].nil? && !options[:no_prompt]
410
+ price_sets = []
411
+ while Morpheus::Cli::OptionTypes.confirm("Add #{price_sets.empty? ? '' : 'another '}price set?", {:default => false}) do
412
+ price_unit = prompt_price_unit(options)
413
+
414
+ avail_price_sets ||= @price_sets_interface.list['priceSets'].collect {|it| {'name' => it['name'], 'value' => it['id'], 'priceUnit' => it['priceUnit']}}
415
+
416
+ if price_set_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'priceSet', 'type' => 'select', 'fieldLabel' => 'Price Set', 'selectOptions' => avail_price_sets.reject {|it| it['priceUnit'] != price_unit}, 'required' => false, 'description' => 'Select Price.'}],options[:options],@api_client,{}, options[:no_prompt], true)['priceSet']
417
+ price_set = avail_price_sets.find {|it| it['value'] == price_set_id}
418
+ price_sets << {'id' => price_set['value'], 'priceUnit' => price_set['priceUnit']}
419
+ avail_price_sets.reject! {|it| it['value'] == price_set_id}
420
+ end
421
+
422
+ if avail_price_sets.empty?
423
+ break
424
+ end
425
+ end
426
+ params['priceSets'] = price_sets if !price_sets.empty?
427
+ end
428
+
429
+ # permissions
430
+ if !options[:no_prompt]
431
+ perms = prompt_permissions(options, ['plans', 'groupDefaults'])
432
+ params['visibility'] = perms.delete('resourcePool')['visibility']
433
+ params['permissions'] = perms
434
+ end
435
+ payload = {'servicePlan' => params}
436
+ end
437
+
438
+ @service_plans_interface.setopts(options)
439
+ if options[:dry_run]
440
+ print_dry_run @service_plans_interface.dry.create(payload)
441
+ return
442
+ end
443
+ json_response = @service_plans_interface.create(payload)
444
+
445
+ if options[:json]
446
+ puts as_json(json_response, options)
447
+ elsif !options[:quiet]
448
+ if json_response['success']
449
+ print_green_success "Service plan created"
450
+ _get(json_response['id'], options)
451
+ else
452
+ print_red_alert "Error creating service plan: #{json_response['msg'] || json_response['errors']}"
453
+ end
454
+ end
455
+ return 0
456
+ rescue RestClient::Exception => e
457
+ print_rest_exception(e, options)
458
+ exit 1
459
+ end
460
+ end
461
+
462
+ def update(args)
463
+ options = {}
464
+ params = {}
465
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
466
+ opts.banner = subcommand_usage("[plan]")
467
+ opts.on("--name NAME", String, "Service plan name") do |val|
468
+ params['name'] = val.to_s
469
+ end
470
+ opts.on("--code CODE", String, "Service plan code, unique identifier") do |val|
471
+ params['code'] = val.to_s
472
+ end
473
+ opts.on('-t', "--provision-type [TYPE]", String, "Provision type ID or code") do |val|
474
+ options[:provisionType] = val
475
+ end
476
+ opts.on("--description [TEXT]", String, "Description") do |val|
477
+ params['description'] = val.to_s
478
+ end
479
+ opts.on('--editable [on|off]', String, "Can be used to enable / disable the editability of the service plan. Default is on") do |val|
480
+ params['editable'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
481
+ end
482
+ opts.on('--storage [AMOUNT]', String, "Storage size is required. Assumes GB unless optional modifier specified, ex: 512MB" ) do |val|
483
+ bytes = parse_bytes_param(val, '--storage', 'GB')
484
+ params['maxStorage'] = bytes[:bytes]
485
+ (params['config'] ||= {})['storageSizeType'] = bytes[:unit].downcase
486
+ end
487
+ opts.on('--memory [AMOUNT]', String, "Memory size is required. Assumes MB unless optional modifier specified, ex: 1GB" ) do |val|
488
+ bytes = parse_bytes_param(val, '--memory', 'MB')
489
+ params['maxMemory'] = bytes[:bytes]
490
+ (params['config'] ||= {})['memorySizeType'] = bytes[:unit].downcase
491
+ end
492
+ opts.on('--cores [NUMBER]', Integer, "Core count. Default is 1" ) do |val|
493
+ params['maxCores'] = val.to_i || 1
494
+ end
495
+ opts.on('--disks [NUMBER]', Integer, "Max disks allowed" ) do |val|
496
+ params['maxDisks'] = val.to_i || 1
497
+ end
498
+ opts.on('--custom-cores [on|off]', String, "Can be used to enable / disable customizable cores. Default is on") do |val|
499
+ params['customCores'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
500
+ end
501
+ opts.on('--custom-storage [on|off]', String, "Can be used to enable / disable customizable storage. Default is on") do |val|
502
+ params['customMaxStorage'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
503
+ end
504
+ opts.on('--custom-volumes [on|off]', String, "Can be used to enable / disable customizable extra volumes. Default is on") do |val|
505
+ params['customMaxDataStorage'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
506
+ end
507
+ opts.on('--custom-memory [on|off]', String, "Can be used to enable / disable customizable memory. Default is on") do |val|
508
+ params['customMaxMemory'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
509
+ end
510
+ opts.on('--add-volumes [on|off]', String, "Can be used to enable / disable ability to add volumes. Default is on") do |val|
511
+ params['addVolumes'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
512
+ end
513
+ opts.on('--sort-order NUMBER', Integer, "Sort order") do |val|
514
+ params['sortOrder'] = val.to_i
515
+ end
516
+ opts.on('--price-sets [LIST]', Array, 'Price set(s), comma separated list of price set IDs') do |list|
517
+ params['priceSets'] = list.collect {|it| it.to_s.strip.empty? || !it.to_i ? nil : it.to_s.strip}.compact.uniq.collect {|it| {'id' => it.to_i}}
518
+ end
519
+ opts.on('--min-storage NUMBER', String, "Min storage. Assumes GB unless optional modifier specified, ex: 512MB") do |val|
520
+ # Storage doesn't get converted to bytes
521
+ bytes = parse_bytes_param(val, '--min-storage', 'GB')
522
+ ((params['config'] ||= {})['ranges'] ||= {})['minStorage'] = bytes[:number]
523
+ (params['config'] ||= {})['storageSizeType'] = bytes[:unit].downcase
524
+ end
525
+ opts.on('--max-storage NUMBER', String, "Max storage. Assumes GB unless optional modifier specified, ex: 512MB") do |val|
526
+ # Storage doesn't get converted to bytes
527
+ bytes = parse_bytes_param(val, '--max-storage', 'GB')
528
+ ((params['config'] ||= {})['ranges'] ||= {})['maxStorage'] = bytes[:number]
529
+ (params['config'] ||= {})['storageSizeType'] = bytes[:unit].downcase
530
+ end
531
+ opts.on('--min-memory NUMBER', String, "Min memory. Assumes MB unless optional modifier specified, ex: 1GB") do |val|
532
+ # Memory does get converted to bytes
533
+ bytes = parse_bytes_param(val, '--min-memory', 'MB')
534
+ ((params['config'] ||= {})['ranges'] ||= {})['minMemory'] = bytes[:bytes]
535
+ (params['config'] ||= {})['memorySizeType'] = bytes[:unit].downcase
536
+ end
537
+ opts.on('--max-memory NUMBER', String, "Max memory. Assumes MB unless optional modifier specified, ex: 1GB") do |val|
538
+ # Memory does get converted to bytes
539
+ bytes = parse_bytes_param(val, '--max-memory', 'MB')
540
+ ((params['config'] ||= {})['ranges'] ||= {})['maxMemory'] = bytes[:bytes]
541
+ (params['config'] ||= {})['memorySizeType'] = bytes[:unit].downcase
542
+ end
543
+ opts.on('--min-cores NUMBER', String, "Min cores") do |val|
544
+ ((params['config'] ||= {})['ranges'] ||= {})['minCores'] = val.to_i
545
+ end
546
+ opts.on('--max-cores NUMBER', String, "Max cores") do |val|
547
+ ((params['config'] ||= {})['ranges'] ||= {})['maxCores'] = val.to_i
548
+ end
549
+ add_perms_options(opts, options, ['plans', 'groupDefaults'])
550
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote, :quiet])
551
+ opts.footer = "Update service plan.\n[plan] is required. Service plan ID, name or code"
552
+ end
553
+ optparse.parse!(args)
554
+ connect(options)
555
+ if args.count != 1
556
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
557
+ return 1
558
+ end
559
+
560
+ print options
561
+ begin
562
+ plan = find_service_plan(args[0])
563
+
564
+ if plan.nil?
565
+ print_red_alert "Service plan #{args[0]} not found"
566
+ exit 1
567
+ end
568
+
569
+ payload = parse_payload(options)
570
+
571
+ if !payload
572
+ # provision type
573
+ options[:provisionType] = options[:provisionType] || (args.count > 1 ? args[1] : nil)
574
+
575
+ if !options[:provisionType].nil?
576
+ provision_types = @service_plans_interface.provision_types()['provisionTypes']
577
+ provision_type = provision_types.find {|it| it['name'] == options[:provisionType] || it['code'] == options[:provisionType] || it['id'] == options[:provisionType].to_i}
578
+
579
+ if provision_type.nil?
580
+ print_red_alert "Provision type #{options[:provisionType]} not found"
581
+ exit 1
582
+ end
583
+ params['provisionType'] = {'id' => provision_type['id']} if !provision_type.nil?
584
+ end
585
+
586
+ # perms
587
+ resource_perms = {}
588
+ resource_perms['all'] = true if options[:groupAccessAll]
589
+ resource_perms['sites'] = options[:groupAccessList].collect {|site_id| {'id' => site_id.to_i}} if !options[:groupAccessList].nil?
590
+
591
+ if !resource_perms.empty? || !options[:tenants].nil?
592
+ params['permissions'] = {}
593
+ params['permissions']['resourcePermissions'] = resource_perms if !resource_perms.empty?
594
+ params['permissions']['tenantPermissions'] = {'accounts' => options[:tenants]} if !options[:tenants].nil?
595
+ end
596
+
597
+ # visibility
598
+ params['visibility'] = options[:visibility] if !options[:visibility].nil?
599
+
600
+ payload = {'servicePlan' => params}
601
+ end
602
+
603
+ if payload.empty? || !payload['servicePlan'] || payload['servicePlan'].empty?
604
+ print_green_success "Nothing to update"
605
+ exit 1
606
+ end
607
+
608
+ @service_plans_interface.setopts(options)
609
+ if options[:dry_run]
610
+ print_dry_run @service_plans_interface.dry.update(plan['id'], payload)
611
+ return
612
+ end
613
+ json_response = @service_plans_interface.update(plan['id'], payload)
614
+
615
+ if options[:json]
616
+ puts as_json(json_response, options)
617
+ elsif !options[:quiet]
618
+ if json_response['success']
619
+ print_green_success "Service plan updated"
620
+ _get(plan['id'], options)
621
+ else
622
+ print_red_alert "Error updating service plan: #{json_response['msg'] || json_response['errors']}"
623
+ end
624
+ end
625
+ return 0
626
+ rescue RestClient::Exception => e
627
+ print_rest_exception(e, options)
628
+ exit 1
629
+ end
630
+ end
631
+
632
+ def deactivate(args)
633
+ options = {}
634
+ params = {}
635
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
636
+ opts.banner = subcommand_usage( "[plan]")
637
+ build_common_options(opts, options, [:json, :dry_run, :remote])
638
+ opts.footer = "Deactivate service plan.\n" +
639
+ "[plan] is required. Service plan ID, name or code"
640
+ end
641
+ optparse.parse!(args)
642
+ connect(options)
643
+ if args.count != 1
644
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
645
+ return 1
646
+ end
647
+
648
+ begin
649
+ plan = find_service_plan(args[0])
650
+
651
+ if !plan
652
+ print_red_alert "Service plan #{args[0]} not found"
653
+ exit 1
654
+ end
655
+
656
+ if plan['active'] == false
657
+ print_green_success "Service plan #{plan['name']} already deactived."
658
+ return 0
659
+ end
660
+
661
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to deactivate the service plan '#{plan['name']}'?", options)
662
+ return 9, "aborted command"
663
+ end
664
+
665
+ @service_plans_interface.setopts(options)
666
+ if options[:dry_run]
667
+ print_dry_run @service_plans_interface.dry.deactivate(plan['id'], params)
668
+ return
669
+ end
670
+
671
+ json_response = @service_plans_interface.deactivate(plan['id'], params)
672
+
673
+ if options[:json]
674
+ print JSON.pretty_generate(json_response)
675
+ print "\n"
676
+ elsif !options[:quiet]
677
+ print_green_success "Service plan #{plan['name']} deactivated"
678
+ end
679
+ return 0
680
+ rescue RestClient::Exception => e
681
+ print_rest_exception(e, options)
682
+ exit 1
683
+ end
684
+ end
685
+
686
+ private
687
+
688
+ def find_service_plan(val)
689
+ (val.to_s =~ /\A\d{1,}\Z/) ? @service_plans_interface.get(val.to_i)['servicePlan'] : @service_plans_interface.list({'code' => val, 'name' => val})["servicePlans"].first
690
+ end
691
+
692
+ def find_provision_type(val)
693
+ (val.to_s =~ /\A\d{1,}\Z/) ? @provision_types_interface.get(val.to_i)['provisionType'] : @provision_types_interface.list({'name' => val})["provisionTypes"].first
694
+ end
695
+
696
+ def printable_byte_size(plan, val, config_field, default_unit = 'MB')
697
+ label = (((plan['config'] && plan['config'][config_field]) || default_unit) == 'MB' || val.to_i < 1024 * 1024 * 1024) ? 'MB' : 'GB'
698
+ val = (val.to_i || 0) / (label == 'MB' ? 1024 * 1024 : 1024 * 1024 * 1024)
699
+ "#{val} #{label}"
700
+ end
701
+
702
+ def get_range(min_val, max_val)
703
+ if min_val && max_val
704
+ "#{min_val} - #{max_val}"
705
+ elsif min_val
706
+ "> #{min_val}"
707
+ elsif max_val
708
+ "< #{max_val}"
709
+ else
710
+ ""
711
+ end
712
+ end
713
+
714
+ def parse_bytes_param(bytes_param, option, assumed_unit = nil)
715
+ if bytes_param && bytes_param.to_f > 0
716
+ bytes_param.upcase!
717
+ multiplier = 1
718
+ unit = nil
719
+ number = (bytes_param.to_f == bytes_param.to_i ? bytes_param.to_i : bytes_param.to_f)
720
+ if (bytes_param.end_with? 'GB') || ((!bytes_param.end_with? 'MB') && assumed_unit == 'GB')
721
+ unit = 'GB'
722
+ multiplier = 1024 * 1024 * 1024
723
+ elsif (bytes_param.end_with? 'MB') || assumed_unit == 'MB'
724
+ unit = 'MB'
725
+ multiplier = 1024 * 1024
726
+ end
727
+ return {:bytes_param => bytes_param, :bytes => number * multiplier, :number => number, :multiplier => multiplier, :unit => unit}
728
+ end
729
+ raise_command_error "Invalid value for #{option} option"
730
+ end
731
+
732
+ def prompt_price_unit(options)
733
+ price_units = ['Minute', 'Hour', 'Day', 'Month', 'Year', '2 Year', '3 Year', '4 Year', '5 Year'].collect {|it| {'name' => it, 'value' => it.downcase.delete(' ')}}
734
+ Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'priceUnit', 'type' => 'select', 'fieldLabel' => 'Price Unit', 'selectOptions' => price_units, 'required' => true, 'defaultValue' => 'hour', 'description' => 'Select Price Unit.'}],options[:options],@api_client,{})['priceUnit']
735
+ end
736
+ end