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
@@ -0,0 +1,669 @@
1
+ require 'morpheus/cli/cli_command'
2
+ require 'money'
3
+
4
+ class Morpheus::Cli::PricesCommand
5
+ include Morpheus::Cli::CliCommand
6
+ include Morpheus::Cli::AccountsHelper
7
+ include Morpheus::Cli::ProvisioningHelper
8
+ include Morpheus::Cli::WhoamiHelper
9
+
10
+ set_command_name :'prices'
11
+
12
+ register_subcommands :list, :get, :add, :update, :deactivate
13
+ set_default_subcommand :list
14
+
15
+ def connect(opts)
16
+ @api_client = establish_remote_appliance_connection(opts)
17
+ @prices_interface = @api_client.prices
18
+ @accounts_interface = @api_client.accounts
19
+ end
20
+
21
+ def handle(args)
22
+ handle_subcommand(args)
23
+ end
24
+
25
+ def list(args)
26
+ options = {}
27
+ params = {}
28
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
29
+ opts.banner = subcommand_usage()
30
+ opts.on('-i', '--include-inactive [on|off]', String, "Can be used to enable / disable inactive filter. Default is on") do |val|
31
+ params['includeInactive'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
32
+ end
33
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
34
+ opts.footer = "List prices."
35
+ end
36
+ optparse.parse!(args)
37
+ connect(options)
38
+ if args.count != 0
39
+ raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
40
+ return 1
41
+ end
42
+
43
+ begin
44
+ params.merge!(parse_list_options(options))
45
+
46
+ @prices_interface.setopts(options)
47
+ if options[:dry_run]
48
+ print_dry_run @prices_interface.dry.list(params)
49
+ return
50
+ end
51
+ json_response = @prices_interface.list(params)
52
+
53
+ render_result = render_with_format(json_response, options, 'prices')
54
+ return 0 if render_result
55
+
56
+ title = "Morpheus Prices"
57
+ subtitles = []
58
+ subtitles += parse_list_subtitles(options)
59
+ print_h1 title, subtitles
60
+
61
+ prices = json_response['prices']
62
+ if prices.empty?
63
+ print yellow,"No prices found.",reset,"\n"
64
+ else
65
+ rows = prices.collect do |it|
66
+ {
67
+ id: (it['active'] ? cyan : yellow) + it['id'].to_s,
68
+ name: it['name'],
69
+ active: format_boolean(it['active']),
70
+ priceType: price_type_label(it['priceType']),
71
+ tenant: it['account'].nil? ? (is_master_account ? 'All Tenants' : nil) : it['account']['name'],
72
+ priceUnit: it['priceUnit'].nil? ? nil : it['priceUnit'].capitalize,
73
+ priceAdjustment: it['markupType'].nil? ? 'None' : it['markupType'].capitalize,
74
+ cost: price_prefix(it) + format_amount(it['cost'] || 0),
75
+ markup: price_markup(it),
76
+ price: price_prefix(it) + format_amount(it['markupType'] == 'custom' ? it['customPrice'] || 0 : it['price'] || 0) + cyan
77
+ }
78
+ end
79
+ columns = [
80
+ :id, :name, :active, :priceType, :tenant, :priceUnit, :priceAdjustment, :cost, :markup, :price
81
+ ]
82
+ columns.delete(:active) if !params['includeInactive']
83
+
84
+ print as_pretty_table(rows, columns, options)
85
+ print_results_pagination(json_response)
86
+ print reset,"\n"
87
+ end
88
+ print reset,"\n"
89
+ return 0
90
+ rescue RestClient::Exception => e
91
+ print_rest_exception(e, options)
92
+ exit 1
93
+ end
94
+ end
95
+
96
+ def get(args)
97
+ options = {}
98
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
99
+ opts.banner = subcommand_usage("[price]")
100
+ build_common_options(opts, options, [:json, :dry_run, :remote])
101
+ opts.footer = "Get details about a price.\n" +
102
+ "[price] is required. Price ID, name or code"
103
+ end
104
+ optparse.parse!(args)
105
+ if args.count != 1
106
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
107
+ end
108
+ connect(options)
109
+ return _get(args[0], options)
110
+ end
111
+
112
+ def _get(price_id, options = {})
113
+ params = {}
114
+ begin
115
+ @prices_interface.setopts(options)
116
+
117
+ if !(price_id.to_s =~ /\A\d{1,}\Z/)
118
+ price = find_price(price_id)
119
+
120
+ if !price
121
+ print_red_alert "Price #{price_id} not found"
122
+ exit 1
123
+ end
124
+ price_id = price['id']
125
+ end
126
+
127
+ if options[:dry_run]
128
+ print_dry_run @prices_interface.dry.get(price_id)
129
+ return
130
+ end
131
+ json_response = @prices_interface.get(price_id)
132
+
133
+ render_result = render_with_format(json_response, options, 'price')
134
+ return 0 if render_result
135
+
136
+ title = "Morpheus Price"
137
+ subtitles = []
138
+ subtitles += parse_list_subtitles(options)
139
+ print_h1 title, subtitles
140
+
141
+ price = json_response['price']
142
+ print cyan
143
+ description_cols = {
144
+ "ID" => lambda {|it| it['id']},
145
+ "Name" => lambda {|it| it['name']},
146
+ "Code" => lambda {|it| it['code']},
147
+ "Tenant" => lambda {|it| it['account'].nil? ? (is_master_account ? 'All Tenants' : nil) : it['account']['name']},
148
+ "Price Type" => lambda {|it| price_type_label(it['priceType'])}
149
+ }
150
+
151
+ if price['priceType'] == 'platform'
152
+ description_cols['Platform'] = lambda {|it| it['platform'].nil? ? nil : it['platform'].capitalize}
153
+ elsif price['priceType'] == 'software'
154
+ description_cols['Software'] = lambda {|it| it['software'].nil? ? nil : it['software']}
155
+ elsif price['priceType'] == 'storage'
156
+ description_cols['Volume Type'] = lambda {|it| it['volumeType'].nil? ? nil : it['volumeType']['name']}
157
+ elsif price['priceType'] == 'datastore'
158
+ description_cols['Data Store'] = lambda {|it| it['datastore'].nil? ? nil : it['datastore']['name']}
159
+ description_cols['Apply Across Clouds'] = lambda {|it| it['crossCloudApply'] == true ? 'On' : 'Off'}
160
+ end
161
+
162
+ description_cols['Price Unit'] = lambda {|it| it['priceUnit'].nil? ? nil : it['priceUnit'].capitalize}
163
+ description_cols['Incur Charges'] = lambda {|it| it['incurCharges'].nil? ? nil : (it['incurCharges'] != 'always' ? 'While ' : '') + it['incurCharges'].capitalize}
164
+ description_cols['Currency'] = lambda {|it| (it['currency'] || '').upcase}
165
+ description_cols['Cost'] = lambda {|it| price_prefix(it) + format_amount(it['cost'] || 0)}
166
+ description_cols['Price Adjustment'] = lambda {|it| it['markupType'].nil? ? 'None' : it['markupType'].capitalize}
167
+
168
+ if ['fixed', 'percent'].include?(price['markupType'])
169
+ description_cols['Markup'] = lambda {|it| price_markup(it)}
170
+ end
171
+
172
+ description_cols['Custom Price'] = lambda {|it| price_prefix(it) + format_amount(it['customPrice'] || 0)}
173
+
174
+ print_description_list(description_cols, price)
175
+ print reset,"\n"
176
+ return 0
177
+ rescue RestClient::Exception => e
178
+ print_rest_exception(e, options)
179
+ exit 1
180
+ end
181
+ end
182
+
183
+ def add(args)
184
+ options = {}
185
+ params = {}
186
+
187
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
188
+ opts.banner = subcommand_usage("[name] [code]")
189
+ opts.on("--name NAME", String, "Price name") do |val|
190
+ params['name'] = val.to_s
191
+ end
192
+ opts.on("--code CODE", String, "Price code, unique identifier") do |val|
193
+ params['code'] = val.to_s
194
+ end
195
+ opts.on("--tenant [ACCOUNT|all]", String, "Tenant account or all. Apply price to all tenants when not set.") do |val|
196
+ options[:tenant] = val
197
+ end
198
+ opts.on("--type [TYPE]", String, "Price type") do |val|
199
+ if price_types[val]
200
+ params['priceType'] = val
201
+ else
202
+ raise_command_error "Invalid price type '#{val}'. Available price types: #{price_types.keys.join(', ')}"
203
+ end
204
+ end
205
+ opts.on("--unit [UNIT]", String, "Price unit") do |val|
206
+ if price_units.include?(val)
207
+ params['priceUnit'] = val
208
+ else
209
+ raise_command_error "Invalid price unit '#{val}'. Available price units: #{price_units.join(', ')}"
210
+ end
211
+ end
212
+ opts.on("--platform [PLATFORM]", String, "Price platform [linux|windows]. Required for platform price type") do |val|
213
+ if ['linux', 'windows'].include?(val)
214
+ params['platform'] = val
215
+ else
216
+ raise_command_error "Invalid platform '#{val}'. Available platforms: linux, windows"
217
+ end
218
+ end
219
+ opts.on("--software [TEXT]", String, "Price software. Required for software price type") do |val|
220
+ params['software'] = val
221
+ end
222
+ opts.on("--volume [TYPE]", String, "Volume type ID or name. Required for storage price type") do |val|
223
+ options[:volumeType] = val
224
+ end
225
+ opts.on("--datastore [DATASTORE]", String, "Datastore ID or name. Required for datastore price type") do |val|
226
+ options[:datastore] = val
227
+ end
228
+ opts.on("--cross-apply [on|off]", String, "Apply price across clouds. Applicable for datastore price type only") do |val|
229
+ options[:crossCloudApply] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
230
+ end
231
+ opts.on("--incur [WHEN]", String, "Incur charges [running|stopped|always]") do |val|
232
+ if ['running', 'stopped', 'always'].include?(val)
233
+ params['incurCharges'] = val
234
+ else
235
+ raise_command_error "Invalid incur charges '#{val}'. Available options: running, stopped, always"
236
+ end
237
+ end
238
+ opts.on("--currency [CURRENCY]", String, "Price currency") do |val|
239
+ if avail_currencies.include?(val.upcase)
240
+ params['currency'] = val.upcase
241
+ else
242
+ raise_command_error "Unsupported currency '#{val}'. Available currencies: #{avail_currencies.join(', ')}"
243
+ end
244
+ end
245
+ opts.on("--cost [AMOUNT]", Float, "Price cost") do |val|
246
+ params['cost'] = val
247
+ end
248
+ opts.on("--fixed-markup [AMOUNT]", Float, "Add fixed price adjustment") do |val|
249
+ params['markupType'] = 'fixed'
250
+ params['markup'] = val
251
+ end
252
+ opts.on("--percent-markup [PERCENT]", Float, "Add percent price adjustment") do |val|
253
+ params['markupType'] = 'percent'
254
+ params['markupPercent'] = val
255
+ end
256
+ opts.on("--custom-price [AMOUNT]", Float, "Set customer price directly. Can be used to override price calculation based on cost and markup") do |val|
257
+ params['markupType'] = 'custom'
258
+ params['customPrice'] = val
259
+ end
260
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote, :quiet])
261
+ opts.footer = "Create price"
262
+ end
263
+ optparse.parse!(args)
264
+ connect(options)
265
+ if args.count > 2
266
+ raise_command_error "wrong number of arguments, expected 0-2 and got (#{args.count}) #{args}\n#{optparse}"
267
+ return 1
268
+ end
269
+
270
+ begin
271
+ payload = parse_payload(options)
272
+
273
+ if !payload
274
+ # name
275
+ params['name'] ||= args[0] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => 'Price Set Name', 'required' => true, 'description' => 'Price Set Name.'}],options[:options],@api_client,{}, options[:no_prompt])['name']
276
+
277
+ # code
278
+ params['code'] ||= args[1] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'code', 'type' => 'text', 'fieldLabel' => 'Price Set Code', 'required' => true, 'defaultValue' => params['name'].gsub(/[^0-9a-z ]/i, '').gsub(' ', '.').downcase, 'description' => 'Price Set Code.'}],options[:options],@api_client,{}, options[:no_prompt])['code']
279
+
280
+ # tenant
281
+ if options[:tenant].nil?
282
+ account_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'account', 'type' => 'select', 'fieldLabel' => 'Tenant', 'required' => false, 'description' => 'Assign price to tenant', 'selectOptions' => accounts_interface.list()['accounts'].collect {|it| {'name' => it['name'], 'value' => it['id']}}}], options[:options], @api_client, {}, options[:no_prompt])['account']
283
+ if account_id
284
+ params['account'] = {'id' => account_id}
285
+ end
286
+ elsif options[:tenant] != 'all'
287
+ if account = find_account_by_name_or_id(options[:tenant])
288
+ params['account'] = {'id' => account['id']}
289
+ else
290
+ print_red_alert "Tenant #{options[:tenant]} not found"
291
+ exit 1
292
+ end
293
+ end
294
+
295
+ # type (platform, software, datastore, storage)
296
+ params['priceType'] ||= Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'select', 'fieldLabel' => 'Price Type', 'required' => true, 'description' => 'Select price type', 'selectOptions' => price_types.collect {|k,v| {'name' => v, 'value' => k}}}], options[:options], @api_client, {}, options[:no_prompt])['value']
297
+
298
+ # price type
299
+ prompt_for_price_type(params, options)
300
+
301
+ # unit
302
+ params['priceUnit'] ||= Morpheus::Cli::OptionTypes.prompt(['fieldName' => 'value', 'type' => 'select', 'fieldLabel' => 'Price Unit', 'required' => true, 'description' => 'Select price unit', 'defaultValue' => 'month', 'selectOptions' => price_units.collect {|it| {'name' => it.split(' ').collect {|it| it.capitalize}.join(' '), 'value' => it}}], options[:options], @api_client, {}, options[:no_prompt])['value']
303
+
304
+ # incur
305
+ params['incurCharges'] ||= Morpheus::Cli::OptionTypes.prompt(['fieldName' => 'value', 'type' => 'select', 'fieldLabel' => 'Incur Charges', 'required' => true, 'description' => 'Select when to incur charges', 'defaultValue' => 'running', 'selectOptions' => [{'name' => 'When Running', 'value' => 'running'}, {'name' => 'When Stopped', 'value' => 'stopped'}, {'name' => 'Always', 'value' => 'always'}]], options[:options], @api_client, {}, options[:no_prompt])['value']
306
+
307
+ # currency
308
+ params['currency'] ||= Morpheus::Cli::OptionTypes.prompt(['fieldName' => 'value', 'type' => 'select', 'fieldLabel' => 'Currency', 'required' => true, 'description' => 'Select when to incur charges', 'defaultValue' => 'USD', 'selectOptions' => avail_currencies.collect {|it| {'value' => it}}], options[:options], @api_client, {}, options[:no_prompt])['value']
309
+
310
+ # cost
311
+ if params['cost'].nil?
312
+ params['cost'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'number', 'fieldLabel' => 'Cost', 'required' => true, 'description' => 'Price cost', 'defaultValue' => 0.0}],options[:options],@api_client,{}, options[:no_prompt])['value']
313
+ end
314
+
315
+ # adjustment / markup type
316
+ if params['markupType'].nil?
317
+ markup_type = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'select', 'fieldLabel' => 'Price Adjustment', 'required' => false, 'description' => 'Price Adjustment', 'selectOptions' => [{'name' => 'None', 'value' => 'none'}, {'name' => 'Fixed Markup', 'value' => 'fixed'}, {'name' => 'Percent Markup', 'value' => 'percent'}, {'name' => 'Custom Price', 'value' => 'custom'}], 'defaultValue' => 'none'}],options[:options],@api_client,{}, options[:no_prompt])['value']
318
+
319
+ if markup_type && markup_type != 'none'
320
+ params['markupType'] = markup_type
321
+ end
322
+ end
323
+
324
+ prompt_for_markup_type(params, options)
325
+
326
+ payload = {'price' => params}
327
+ end
328
+
329
+ @prices_interface.setopts(options)
330
+ if options[:dry_run]
331
+ print_dry_run @prices_interface.dry.create(payload)
332
+ return
333
+ end
334
+ json_response = @prices_interface.create(payload)
335
+
336
+ if options[:json]
337
+ puts as_json(json_response, options)
338
+ elsif !options[:quiet]
339
+ if json_response['success']
340
+ print_green_success "Price created"
341
+ _get(json_response['id'], options)
342
+ else
343
+ print_red_alert "Error creating price: #{json_response['msg'] || json_response['errors']}"
344
+ end
345
+ end
346
+ return 0
347
+ rescue RestClient::Exception => e
348
+ print_rest_exception(e, options)
349
+ exit 1
350
+ end
351
+ end
352
+
353
+ def update(args)
354
+ options = {}
355
+ params = {}
356
+
357
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
358
+ opts.banner = subcommand_usage("[price]")
359
+ opts.on("--name NAME", String, "Price name") do |val|
360
+ params['name'] = val.to_s
361
+ end
362
+ opts.on("--type [TYPE]", String, "Price type") do |val|
363
+ if price_types[val]
364
+ params['priceType'] = val
365
+ else
366
+ raise_command_error "Invalid price type '#{val}'. Available price types: #{price_types.keys.join(', ')}"
367
+ end
368
+ end
369
+ opts.on("--unit [UNIT]", String, "Price unit") do |val|
370
+ if price_units.include?(val)
371
+ params['priceUnit'] = val
372
+ else
373
+ raise_command_error "Invalid price unit '#{val}'. Available price units: #{price_units.join(', ')}"
374
+ end
375
+ end
376
+ opts.on("--platform [PLATFORM]", String, "Price platform [linux|windows]. Required for platform price type") do |val|
377
+ if ['linux', 'windows'].include?(val)
378
+ params['platform'] = val
379
+ else
380
+ raise_command_error "Invalid platform '#{val}'. Available platforms: linux, windows"
381
+ end
382
+ end
383
+ opts.on("--software [TEXT]", String, "Price software. Required for software price type") do |val|
384
+ params['software'] = val
385
+ end
386
+ opts.on("--volume [TYPE]", String, "Volume type ID or name. Required for storage price type") do |val|
387
+ options[:volumeType] = val
388
+ end
389
+ opts.on("--datastore [DATASTORE]", String, "Datastore ID or name. Required for datastore price type") do |val|
390
+ options[:datastore] = val
391
+ end
392
+ opts.on("--cross-apply [on|off]", String, "Apply price across clouds. Applicable for datastore price type only") do |val|
393
+ options[:crossCloudApply] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
394
+ end
395
+ opts.on("--incur [WHEN]", String, "Incur charges [running|stopped|always]") do |val|
396
+ if ['running', 'stopped', 'always'].include?(val)
397
+ params['incurCharges'] = val
398
+ else
399
+ raise_command_error "Invalid incur charges '#{val}'. Available options: running, stopped, always"
400
+ end
401
+ end
402
+ opts.on("--currency [CURRENCY]", String, "Price currency") do |val|
403
+ if avail_currencies.include?(val.upcase)
404
+ params['currency'] = val.upcase
405
+ else
406
+ raise_command_error "Unsupported currency '#{val}'. Available currencies: #{avail_currencies.join(', ')}"
407
+ end
408
+ end
409
+ opts.on("--cost [AMOUNT]", Float, "Price cost") do |val|
410
+ params['cost'] = val
411
+ end
412
+ opts.on("--fixed-markup [AMOUNT]", Float, "Add fixed price adjustment") do |val|
413
+ params['markupType'] = 'fixed'
414
+ params['markup'] = val
415
+ end
416
+ opts.on("--percent-markup [PERCENT]", Float, "Add percent price adjustment") do |val|
417
+ params['markupType'] = 'percent'
418
+ params['markupPercent'] = val
419
+ end
420
+ opts.on("--custom-price [AMOUNT]", Float, "Set customer price directly. Can be used to override price calculation based on cost and markup") do |val|
421
+ params['markupType'] = 'custom'
422
+ params['customPrice'] = val
423
+ end
424
+ opts.on("--restart-usage [on|off]", String, "Apply price changes to usage. Default is on") do |val|
425
+ params['restartUsage'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
426
+ end
427
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote, :quiet])
428
+ opts.footer = "Update price\n[price] is required. Price ID, name or code"
429
+ end
430
+ optparse.parse!(args)
431
+ connect(options)
432
+ if args.count != 1
433
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
434
+ return 1
435
+ end
436
+
437
+ begin
438
+ price = find_price(args[0])
439
+
440
+ if price.nil?
441
+ print_red_alert "Price #{args[0]} not found"
442
+ exit 1
443
+ end
444
+
445
+ payload = parse_payload(options)
446
+
447
+ if payload.nil?
448
+ # price type
449
+ prompt_for_price_type(params, options, price)
450
+
451
+ # adjustment / markup type
452
+ prompt_for_markup_type(params, options)
453
+
454
+ payload = {'price' => params}
455
+ end
456
+
457
+ if payload['price'].empty?
458
+ print_green_success "Nothing to update"
459
+ return
460
+ end
461
+
462
+ @prices_interface.setopts(options)
463
+ if options[:dry_run]
464
+ print_dry_run @prices_interface.dry.update(price['id'], payload)
465
+ return
466
+ end
467
+ json_response = @prices_interface.update(price['id'], payload)
468
+
469
+ if options[:json]
470
+ puts as_json(json_response, options)
471
+ elsif !options[:quiet]
472
+ if json_response['success']
473
+ print_green_success "Price updated"
474
+ _get(price['id'], options)
475
+ else
476
+ print_red_alert "Error updating price: #{json_response['msg'] || json_response['errors']}"
477
+ end
478
+ end
479
+ return 0
480
+ rescue RestClient::Exception => e
481
+ print_rest_exception(e, options)
482
+ exit 1
483
+ end
484
+ end
485
+
486
+ def deactivate(args)
487
+ options = {}
488
+ params = {}
489
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
490
+ opts.banner = subcommand_usage( "[price]")
491
+ build_common_options(opts, options, [:json, :dry_run, :remote])
492
+ opts.footer = "Deactivate price.\n" +
493
+ "[price] is required. Price ID, name or code"
494
+ end
495
+ optparse.parse!(args)
496
+ connect(options)
497
+ if args.count != 1
498
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
499
+ return 1
500
+ end
501
+
502
+ begin
503
+ price = find_price(args[0])
504
+
505
+ if !price
506
+ print_red_alert "Price #{args[0]} not found"
507
+ exit 1
508
+ end
509
+
510
+ if price['active'] == false
511
+ print_green_success "Price #{price_set['name']} already deactived."
512
+ return 0
513
+ end
514
+
515
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to deactivate the price '#{price['name']}'?", options)
516
+ return 9, "aborted command"
517
+ end
518
+
519
+ @prices_interface.setopts(options)
520
+ if options[:dry_run]
521
+ print_dry_run @prices_interface.dry.deactivate(price['id'], params)
522
+ return
523
+ end
524
+
525
+ json_response = @prices_interface.deactivate(price['id'], params)
526
+
527
+ if options[:json]
528
+ print JSON.pretty_generate(json_response)
529
+ print "\n"
530
+ elsif !options[:quiet]
531
+ print_green_success "Price #{price['name']} deactivate"
532
+ end
533
+ return 0
534
+ rescue RestClient::Exception => e
535
+ print_rest_exception(e, options)
536
+ exit 1
537
+ end
538
+ end
539
+
540
+ private
541
+
542
+ def find_price(val)
543
+ (val.to_s =~ /\A\d{1,}\Z/) ? @prices_interface.get(val.to_i)['price'] : @prices_interface.list({'code' => val, 'name' => val})['prices'].first
544
+ end
545
+
546
+ def find_datastore(val)
547
+ (val.to_s =~ /\A\d{1,}\Z/) ? @prices_interface.get_datastore(val.to_i)['datastore'] : @prices_interface.list_datastores({'name' => val})['datastores'].first
548
+ end
549
+
550
+ def find_volume_type(val)
551
+ (val.to_s =~ /\A\d{1,}\Z/) ? @prices_interface.get_volume_type(val.to_i)['volumeType'] : @prices_interface.list_volume_types({'name' => val})['volumeTypes'].first
552
+ end
553
+
554
+ def currency_sym(currency)
555
+ Money::Currency.new((currency || 'usd').to_sym).symbol
556
+ end
557
+
558
+ def price_prefix(price)
559
+ (['platform', 'software'].include?(price['priceType']) ? '+' : '') + currency_sym(price['currency'])
560
+ end
561
+
562
+ def price_markup(price)
563
+ if price['markupType'] == 'fixed'
564
+ currency_sym(price['currency']) + format_amount(price['markup'] || 0)
565
+ elsif price['markupType'] == 'percent'
566
+ (price['markupPercent'] || 0).to_s + '%'
567
+ else
568
+ 'N/A'
569
+ end
570
+ end
571
+
572
+ def price_type_label(type)
573
+ price_types[type] || type.capitalize
574
+ end
575
+
576
+ def price_types
577
+ {
578
+ 'fixed' => 'Everything',
579
+ 'compute' => 'Memory + CPU',
580
+ 'memory' => 'Memory Only',
581
+ 'cores' => 'Cores Only',
582
+ 'storage' => 'Disk Only',
583
+ 'datastore' => 'Datastore',
584
+ 'platform' => 'Platform',
585
+ 'software' => 'Software'
586
+ }
587
+ end
588
+
589
+ def price_units
590
+ ['minute', 'hour', 'day', 'month', 'year', 'two year', 'three year', 'four year', 'five year']
591
+ end
592
+
593
+ def format_amount(amount)
594
+ rtn = amount.to_s
595
+ if rtn.index('.').nil?
596
+ rtn += '.00'
597
+ elsif rtn.split('.')[1].length < 2
598
+ rtn = rtn + (['0'] * (2 - rtn.split('.')[1].length) * '')
599
+ end
600
+ rtn
601
+ end
602
+
603
+ def avail_currencies
604
+ ['CAD','EUR', 'IDR', 'XCD', 'USD', 'XOF', 'NOK', 'AUD', 'XAF', 'NZD', 'MAD', 'DKK', 'GBP', 'CHF', 'XPF', 'ILS', 'ROL', 'TRL','SEK', 'ZAR']
605
+ end
606
+
607
+ def prompt_for_price_type(params, options, price={})
608
+ case params['priceType']
609
+ when 'platform'
610
+ params['platform'] ||= price['platform'] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'select', 'fieldLabel' => 'Platform', 'required' => true, 'description' => 'Select platform for platform price type', 'selectOptions' => [{'name' => 'Linux', 'value' => 'linux'}, {'name' => 'Windows', 'value' => 'windows'}]}], options[:options], @api_client, {}, options[:no_prompt])['value']
611
+ when 'software'
612
+ params['software'] ||= price['software'] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'text', 'fieldLabel' => 'Software', 'required' => true, 'description' => 'Set software for software price type'}], options[:options], @api_client,{}, options[:no_prompt])['value']
613
+ when 'datastore'
614
+ if options[:datastore]
615
+ datastore = find_datastore(options[:datastore])
616
+ if datastore
617
+ params['datastore'] = {'id' => datastore['id']}
618
+ else
619
+ print_red_alert "Datastore #{options[:datastore]} not found"
620
+ exit 1
621
+ end
622
+ else
623
+ datastore_id = Morpheus::Cli::OptionTypes.prompt(['fieldName' => 'value', 'type' => 'select', 'fieldLabel' => 'Datastore', 'required' => true, 'description' => 'Select datastore for datastore price type', 'selectOptions' => @prices_interface.list_datastores['datastores'].collect {|it| {'name' => it['name'], 'value' => it['id']}}], options[:options], @api_client, {}, options[:no_prompt])['value']
624
+ params['datastore'] = {'id' => datastore_id}
625
+ end
626
+
627
+ if options[:crossCloudApply].nil?
628
+ if !options[:no_prompt]
629
+ params['crossCloudApply'] = price['crossCloudApply'] || Morpheus::Cli::OptionTypes.confirm("Apply price across clouds?", {:default => false})
630
+ end
631
+ else
632
+ params['crossCloudApply'] = options[:crossCloudApply]
633
+ end
634
+ when 'storage'
635
+ if options[:volumeType]
636
+ volume_type = find_volume_type(options[:volumeType])
637
+ if volume_type
638
+ params['volumeType'] = {'id' => volume_type['id']}
639
+ else
640
+ print_red_alert "Volume type #{options[:volumeType]} not found"
641
+ exit 1
642
+ end
643
+ else
644
+ volume_type_id = (price['volumeType'] ? price['volumeType']['id'] : Morpheus::Cli::OptionTypes.prompt(['fieldName' => 'value', 'type' => 'select', 'fieldLabel' => 'Volume Type', 'required' => true, 'description' => 'Select volume type for storage price type', 'selectOptions' => @prices_interface.list_volume_types['volumeTypes'].collect {|it| {'name' => it['name'], 'value' => it['id']}}], options[:options], @api_client, {}, options[:no_prompt], true)['value'])
645
+ params['volumeType'] = {'id' => volume_type_id}
646
+ end
647
+ end
648
+ end
649
+
650
+ def prompt_for_markup_type(params, options, price={})
651
+ case params['markupType']
652
+ when 'percent'
653
+ params['markupPercent'] = price['markupPercent'] if params['markupPercent'].nil?
654
+ if params['markupPercent'].nil?
655
+ params['markupPercent'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'number', 'fieldLabel' => 'Markup Percent', 'required' => true, 'description' => 'Markup Percent'}],options[:options],@api_client,{}, options[:no_prompt])['value']
656
+ end
657
+ when 'fixed'
658
+ params['markup'] = price['markup'] if params['markup'].nil?
659
+ if params['markup'].nil?
660
+ params['markup'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'number', 'fieldLabel' => 'Markup Amount', 'required' => true, 'description' => 'Markup Amount'}],options[:options],@api_client,{}, options[:no_prompt])['value']
661
+ end
662
+ when 'custom'
663
+ params['customPrice'] = price['customPrice'] if params['customPrice'].nil?
664
+ if params['customPrice'].nil?
665
+ params['customPrice'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'value', 'type' => 'number', 'fieldLabel' => 'Price', 'required' => true, 'description' => 'Price'}],options[:options],@api_client,{}, options[:no_prompt])['value']
666
+ end
667
+ end
668
+ end
669
+ end