morpheus-cli 4.1.8 → 4.1.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/api_client.rb +24 -0
- data/lib/morpheus/api/{old_cypher_interface.rb → budgets_interface.rb} +10 -11
- data/lib/morpheus/api/cloud_datastores_interface.rb +7 -0
- data/lib/morpheus/api/cloud_resource_pools_interface.rb +2 -2
- data/lib/morpheus/api/cypher_interface.rb +18 -12
- data/lib/morpheus/api/health_interface.rb +72 -0
- data/lib/morpheus/api/instances_interface.rb +1 -1
- data/lib/morpheus/api/library_instance_types_interface.rb +7 -0
- data/lib/morpheus/api/log_settings_interface.rb +6 -0
- data/lib/morpheus/api/network_security_servers_interface.rb +30 -0
- data/lib/morpheus/api/price_sets_interface.rb +42 -0
- data/lib/morpheus/api/prices_interface.rb +68 -0
- data/lib/morpheus/api/provisioning_settings_interface.rb +29 -0
- data/lib/morpheus/api/servers_interface.rb +1 -1
- data/lib/morpheus/api/service_plans_interface.rb +34 -11
- data/lib/morpheus/api/task_sets_interface.rb +8 -0
- data/lib/morpheus/api/tasks_interface.rb +8 -0
- data/lib/morpheus/cli.rb +6 -3
- data/lib/morpheus/cli/appliance_settings_command.rb +13 -5
- data/lib/morpheus/cli/approvals_command.rb +1 -1
- data/lib/morpheus/cli/apps.rb +88 -28
- data/lib/morpheus/cli/backup_settings_command.rb +1 -1
- data/lib/morpheus/cli/blueprints_command.rb +2 -0
- data/lib/morpheus/cli/budgets_command.rb +672 -0
- data/lib/morpheus/cli/cli_command.rb +13 -2
- data/lib/morpheus/cli/cli_registry.rb +1 -0
- data/lib/morpheus/cli/clusters.rb +40 -274
- data/lib/morpheus/cli/commands/standard/benchmark_command.rb +114 -66
- data/lib/morpheus/cli/commands/standard/coloring_command.rb +12 -0
- data/lib/morpheus/cli/commands/standard/curl_command.rb +31 -6
- data/lib/morpheus/cli/commands/standard/echo_command.rb +8 -3
- data/lib/morpheus/cli/commands/standard/set_prompt_command.rb +1 -1
- data/lib/morpheus/cli/containers_command.rb +37 -24
- data/lib/morpheus/cli/cypher_command.rb +191 -150
- data/lib/morpheus/cli/health_command.rb +903 -0
- data/lib/morpheus/cli/hosts.rb +43 -32
- data/lib/morpheus/cli/instances.rb +119 -68
- data/lib/morpheus/cli/jobs_command.rb +1 -1
- data/lib/morpheus/cli/library_instance_types_command.rb +61 -11
- data/lib/morpheus/cli/library_option_types_command.rb +2 -2
- data/lib/morpheus/cli/log_settings_command.rb +46 -3
- data/lib/morpheus/cli/logs_command.rb +24 -17
- data/lib/morpheus/cli/mixins/accounts_helper.rb +2 -0
- data/lib/morpheus/cli/mixins/logs_helper.rb +73 -19
- data/lib/morpheus/cli/mixins/print_helper.rb +29 -1
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +554 -96
- data/lib/morpheus/cli/mixins/whoami_helper.rb +13 -1
- data/lib/morpheus/cli/networks_command.rb +3 -0
- data/lib/morpheus/cli/option_types.rb +83 -53
- data/lib/morpheus/cli/price_sets_command.rb +543 -0
- data/lib/morpheus/cli/prices_command.rb +669 -0
- data/lib/morpheus/cli/processes_command.rb +0 -2
- data/lib/morpheus/cli/provisioning_settings_command.rb +237 -0
- data/lib/morpheus/cli/remote.rb +9 -4
- data/lib/morpheus/cli/reports_command.rb +10 -4
- data/lib/morpheus/cli/roles.rb +93 -38
- data/lib/morpheus/cli/security_groups.rb +10 -0
- data/lib/morpheus/cli/service_plans_command.rb +736 -0
- data/lib/morpheus/cli/tasks.rb +220 -8
- data/lib/morpheus/cli/tenants_command.rb +3 -16
- data/lib/morpheus/cli/users.rb +2 -25
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/whitelabel_settings_command.rb +18 -18
- data/lib/morpheus/cli/whoami.rb +28 -10
- data/lib/morpheus/cli/workflows.rb +488 -36
- data/lib/morpheus/formatters.rb +22 -0
- data/morpheus-cli.gemspec +1 -0
- metadata +28 -5
- data/lib/morpheus/cli/accounts.rb +0 -335
- data/lib/morpheus/cli/old_cypher_command.rb +0 -412
@@ -19,7 +19,6 @@ module Morpheus::Cli::WhoamiHelper
|
|
19
19
|
exit 1
|
20
20
|
end
|
21
21
|
@is_master_account = whoami_response["isMasterAccount"]
|
22
|
-
@user_permissions = whoami_response["permissions"]
|
23
22
|
|
24
23
|
if whoami_response["appliance"]
|
25
24
|
@appliance_build_verison = whoami_response["appliance"]["buildVersion"]
|
@@ -30,4 +29,17 @@ module Morpheus::Cli::WhoamiHelper
|
|
30
29
|
return whoami_response
|
31
30
|
end
|
32
31
|
|
32
|
+
def current_account
|
33
|
+
if @current_user.nil?
|
34
|
+
load_whoami
|
35
|
+
end
|
36
|
+
@current_user ? @current_user['account'] : nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def is_master_account
|
40
|
+
if @current_user.nil?
|
41
|
+
load_whoami
|
42
|
+
end
|
43
|
+
@is_master_account
|
44
|
+
end
|
33
45
|
end
|
@@ -457,6 +457,9 @@ class Morpheus::Cli::NetworksCommand
|
|
457
457
|
#option_type['fieldContext'] = nil
|
458
458
|
end
|
459
459
|
network_type_params = Morpheus::Cli::OptionTypes.prompt(network_type_option_types,options[:options],@api_client, {zoneId: cloud['id']})
|
460
|
+
# network context options belong at network level and not network.network
|
461
|
+
network_context_params = network_type_params.delete('network')
|
462
|
+
payload['network'].deep_merge!(network_context_params) if network_context_params
|
460
463
|
payload['network'].deep_merge!(network_type_params)
|
461
464
|
|
462
465
|
#todo: special handling of type: 'aciVxlan'
|
@@ -36,6 +36,7 @@ module Morpheus
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def self.prompt(option_types, options={}, api_client=nil, api_params={}, no_prompt=false, paging_enabled=false)
|
39
|
+
paging_enabled = false if Morpheus::Cli.windows?
|
39
40
|
results = {}
|
40
41
|
options = options || {}
|
41
42
|
# puts "Options Prompt #{options}"
|
@@ -59,40 +60,45 @@ module Morpheus
|
|
59
60
|
namespaces = field_key.split(".")
|
60
61
|
field_name = namespaces.pop
|
61
62
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
value = value.to_s.include?('.') ? value.to_f : value.to_i
|
77
|
-
elsif option_type['type'] == 'select'
|
78
|
-
# this should just fall down through below, with the extra params no_prompt, use_value
|
79
|
-
value = select_prompt(option_type, api_client, (api_params || {}).merge(results), true, value)
|
80
|
-
end
|
81
|
-
if options[:always_prompt] != true
|
82
|
-
value_found = true
|
63
|
+
# respect optionType.dependsOnCode
|
64
|
+
if option_type['dependsOnCode'] && option_type['dependsOnCode'] != ""
|
65
|
+
# optionTypes can have this setting in the format code=value or code:value
|
66
|
+
parts = option_type['dependsOnCode'].include?("=") ? option_type['dependsOnCode'].split("=") : option_type['dependsOnCode'].split(":")
|
67
|
+
depends_on_code = parts[0]
|
68
|
+
depends_on_value = parts[1]
|
69
|
+
depends_on_option_type = option_types.find {|it| it["code"] == depends_on_code }
|
70
|
+
# could not find the dependent option type, proceed and prompt
|
71
|
+
if !depends_on_option_type.nil?
|
72
|
+
# dependent option type has a different value
|
73
|
+
depends_on_field_key = depends_on_option_type['fieldContext'] ? "#{depends_on_option_type['fieldContext']}.#{depends_on_option_type['fieldName']}" : "#{depends_on_option_type['fieldName']}"
|
74
|
+
found_dep_value = get_object_value(results, depends_on_field_key) || get_object_value(options, depends_on_field_key)
|
75
|
+
if depends_on_value && depends_on_value != found_dep_value
|
76
|
+
next
|
83
77
|
end
|
84
78
|
end
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
cur_namespace = options
|
83
|
+
|
84
|
+
namespaces.each do |ns|
|
85
|
+
next if ns.empty?
|
86
|
+
cur_namespace[ns.to_s] ||= {}
|
87
|
+
cur_namespace = cur_namespace[ns.to_s]
|
88
|
+
context_map[ns.to_s] ||= {}
|
89
|
+
context_map = context_map[ns.to_s]
|
90
|
+
end
|
91
|
+
# use the value passed in the options map
|
92
|
+
if cur_namespace.key?(field_name)
|
93
|
+
value = cur_namespace[field_name]
|
94
|
+
if option_type['type'] == 'number'
|
95
|
+
value = value.to_s.include?('.') ? value.to_f : value.to_i
|
96
|
+
elsif option_type['type'] == 'select'
|
97
|
+
# this should just fall down through below, with the extra params no_prompt, use_value
|
98
|
+
value = select_prompt(option_type.merge({'defaultValue' => value}), api_client, (api_params || {}).merge(results), true)
|
99
|
+
end
|
100
|
+
if options[:always_prompt] != true
|
101
|
+
value_found = true
|
96
102
|
end
|
97
103
|
end
|
98
104
|
|
@@ -101,14 +107,12 @@ module Morpheus
|
|
101
107
|
option_type = option_type.clone
|
102
108
|
option_type['defaultValue'] = value
|
103
109
|
end
|
104
|
-
|
105
|
-
|
106
110
|
# no_prompt means skip prompting and instead
|
107
111
|
# use default value or error if a required option is not present
|
108
112
|
no_prompt = no_prompt || options[:no_prompt]
|
109
113
|
if no_prompt
|
110
114
|
if !value_found
|
111
|
-
if option_type['defaultValue'] != nil
|
115
|
+
if option_type['defaultValue'] != nil && option_type['type'] != 'select'
|
112
116
|
value = option_type['defaultValue']
|
113
117
|
value_found = true
|
114
118
|
end
|
@@ -116,7 +120,7 @@ module Morpheus
|
|
116
120
|
# select type is special because it supports skipSingleOption
|
117
121
|
# and prints the available options on error
|
118
122
|
if option_type['type'] == 'select'
|
119
|
-
value = select_prompt(option_type, api_client, (api_params || {}).merge(results), true)
|
123
|
+
value = select_prompt(option_type.merge({'defaultValue' => value}), api_client, (api_params || {}).merge(results), true)
|
120
124
|
value_found = !!value
|
121
125
|
end
|
122
126
|
if !value_found
|
@@ -246,28 +250,40 @@ module Morpheus
|
|
246
250
|
end
|
247
251
|
|
248
252
|
def self.select_prompt(option_type,api_client, api_params={}, no_prompt=false, use_value=nil, paging_enabled=false)
|
253
|
+
paging_enabled = false if Morpheus::Cli.windows?
|
249
254
|
value_found = false
|
250
255
|
value = nil
|
256
|
+
value_field = (option_type['config'] ? option_type['config']['valueField'] : nil) || 'value'
|
251
257
|
default_value = option_type['defaultValue']
|
252
258
|
# local array of options
|
253
259
|
if option_type['selectOptions']
|
254
|
-
|
255
|
-
|
260
|
+
# calculate from inline lambda
|
261
|
+
if option_type['selectOptions'].is_a?(Proc)
|
262
|
+
select_options = option_type['selectOptions'].call()
|
263
|
+
else
|
264
|
+
# todo: better type validation
|
265
|
+
select_options = option_type['selectOptions']
|
266
|
+
end
|
256
267
|
elsif option_type['optionSource']
|
257
|
-
#
|
258
|
-
if option_type['optionSource']
|
268
|
+
# calculate from inline lambda
|
269
|
+
if option_type['optionSource'].is_a?(Proc)
|
270
|
+
select_options = option_type['optionSource'].call()
|
271
|
+
elsif option_type['optionSource'] == 'list'
|
272
|
+
# /api/options/list is a special action for custom OptionTypeLists, just need to pass the optionTypeId parameter
|
259
273
|
select_options = load_source_options(option_type['optionSource'], api_client, {'optionTypeId' => option_type['id']})
|
260
274
|
else
|
275
|
+
# remote optionSource aka /api/options/$optionSource?
|
261
276
|
select_options = load_source_options(option_type['optionSource'], api_client, grails_params(api_params || {}))
|
262
|
-
end
|
277
|
+
end
|
263
278
|
else
|
264
279
|
raise "select_prompt() requires selectOptions or optionSource!"
|
265
280
|
end
|
281
|
+
|
266
282
|
# ensure the preselected value (passed as an option) is in the dropdown
|
267
283
|
if !use_value.nil?
|
268
|
-
|
269
|
-
if !
|
270
|
-
value =
|
284
|
+
matched_option = select_options.find {|opt| opt[value_field].to_s == use_value.to_s || opt['name'].to_s == use_value.to_s }
|
285
|
+
if !matched_option.nil?
|
286
|
+
value = matched_option[value_field]
|
271
287
|
value_found = true
|
272
288
|
else
|
273
289
|
print Term::ANSIColor.red, "\nInvalid Option #{option_type['fieldLabel']}: [#{use_value}]\n\n", Term::ANSIColor.reset
|
@@ -296,14 +312,22 @@ module Morpheus
|
|
296
312
|
if found_default_option
|
297
313
|
default_value = found_default_option['name'] # name is prettier than value
|
298
314
|
end
|
315
|
+
else
|
316
|
+
found_default_option = select_options.find {|opt| opt[value_field].to_s == default_value.to_s}
|
317
|
+
if found_default_option
|
318
|
+
default_value = found_default_option['name'] # name is prettier than value
|
319
|
+
end
|
299
320
|
end
|
300
321
|
end
|
301
322
|
|
302
323
|
if no_prompt
|
303
324
|
if !value_found
|
304
|
-
if !
|
325
|
+
if !default_value.nil? && !select_options.nil? && select_options.find {|it| it['name'] == default_value || it[value_field].to_s == default_value.to_s}
|
305
326
|
value_found = true
|
306
|
-
value = select_options[
|
327
|
+
value = select_options.find {|it| it['name'] == default_value || it[value_field].to_s == default_value.to_s}[value_field]
|
328
|
+
elsif !select_options.nil? && select_options.count > 1 && option_type['autoPickOption'] == true
|
329
|
+
value_found = true
|
330
|
+
value = select_options[0][value_field]
|
307
331
|
elsif option_type['required']
|
308
332
|
print Term::ANSIColor.red, "\nMissing Required Option\n\n", Term::ANSIColor.reset
|
309
333
|
print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
|
@@ -321,9 +345,13 @@ module Morpheus
|
|
321
345
|
end
|
322
346
|
end
|
323
347
|
|
324
|
-
|
325
|
-
if paging_enabled
|
326
|
-
|
348
|
+
paging = nil
|
349
|
+
if paging_enabled
|
350
|
+
option_count = select_options ? select_options.count : 0
|
351
|
+
page_size = Readline.get_screen_size[0] - 6
|
352
|
+
if page_size < option_count
|
353
|
+
paging = {:cur_page => 0, :page_size => page_size, :total => option_count}
|
354
|
+
end
|
327
355
|
end
|
328
356
|
|
329
357
|
while !value_found do
|
@@ -336,7 +364,7 @@ module Morpheus
|
|
336
364
|
if option['name'] && option['name'] =~ /^#{Regexp.escape(s)}/
|
337
365
|
matches << option['name']
|
338
366
|
# elsif option['id'] && option['id'].to_s =~ /^#{Regexp.escape(s)}/
|
339
|
-
elsif option[
|
367
|
+
elsif option[value_field] && option[value_field].to_s == s
|
340
368
|
matches << option['name']
|
341
369
|
end
|
342
370
|
}
|
@@ -349,9 +377,9 @@ module Morpheus
|
|
349
377
|
if input.empty? && default_value
|
350
378
|
input = default_value.to_s
|
351
379
|
end
|
352
|
-
select_option = select_options.find{|b| b['name'] == input || (!b['value'].nil? && b['value'].to_s == input) || (b[
|
380
|
+
select_option = select_options.find{|b| b['name'] == input || (!b['value'].nil? && b['value'].to_s == input) || (!b[value_field].nil? && b[value_field].to_s == input) || (b[value_field].nil? && input.empty?)}
|
353
381
|
if select_option
|
354
|
-
value = select_option[
|
382
|
+
value = select_option[value_field]
|
355
383
|
set_last_select(select_option)
|
356
384
|
elsif !input.nil? && !input.to_s.empty?
|
357
385
|
input = '?'
|
@@ -360,7 +388,9 @@ module Morpheus
|
|
360
388
|
if input == '?'
|
361
389
|
help_prompt(option_type)
|
362
390
|
display_select_options(option_type, select_options, paging)
|
363
|
-
|
391
|
+
if paging
|
392
|
+
paging[:cur_page] = (paging[:cur_page] + 1) * paging[:page_size] < paging[:total] ? paging[:cur_page] + 1 : 0
|
393
|
+
end
|
364
394
|
elsif !value.nil? || option_type['required'] != true
|
365
395
|
value_found = true
|
366
396
|
end
|
@@ -0,0 +1,543 @@
|
|
1
|
+
require 'morpheus/cli/cli_command'
|
2
|
+
require 'money'
|
3
|
+
|
4
|
+
class Morpheus::Cli::PriceSetsCommand
|
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 :'price-sets'
|
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
|
+
@options_interface = @api_client.options
|
18
|
+
@accounts_interface = @api_client.accounts
|
19
|
+
@price_sets_interface = @api_client.price_sets
|
20
|
+
@prices_interface = @api_client.prices
|
21
|
+
@clouds_interface = @api_client.clouds
|
22
|
+
@cloud_resource_pools_interface = @api_client.cloud_resource_pools
|
23
|
+
end
|
24
|
+
|
25
|
+
def handle(args)
|
26
|
+
handle_subcommand(args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def list(args)
|
30
|
+
options = {}
|
31
|
+
params = {'includeZones': true}
|
32
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
33
|
+
opts.banner = subcommand_usage()
|
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 price sets."
|
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
|
+
@price_sets_interface.setopts(options)
|
51
|
+
if options[:dry_run]
|
52
|
+
print_dry_run @price_sets_interface.dry.list(params)
|
53
|
+
return
|
54
|
+
end
|
55
|
+
json_response = @price_sets_interface.list(params)
|
56
|
+
|
57
|
+
render_result = render_with_format(json_response, options, 'priceSets')
|
58
|
+
return 0 if render_result
|
59
|
+
|
60
|
+
title = "Morpheus Price Sets"
|
61
|
+
subtitles = []
|
62
|
+
subtitles += parse_list_subtitles(options)
|
63
|
+
print_h1 title, subtitles
|
64
|
+
|
65
|
+
price_sets = json_response['priceSets']
|
66
|
+
if price_sets.empty?
|
67
|
+
print yellow,"No price sets found.",reset,"\n"
|
68
|
+
else
|
69
|
+
rows = price_sets.collect do |it|
|
70
|
+
{
|
71
|
+
id: (it['active'] ? cyan : yellow) + it['id'].to_s,
|
72
|
+
name: it['name'],
|
73
|
+
active: format_boolean(it['active']),
|
74
|
+
price_unit: it['priceUnit'],
|
75
|
+
type: price_set_type_label(it['type']),
|
76
|
+
price_count: it['prices'].count.to_s + cyan
|
77
|
+
}
|
78
|
+
end
|
79
|
+
columns = [
|
80
|
+
:id, :name, :active, :price_unit, :type, '# of prices' => :price_count
|
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-set]")
|
100
|
+
build_common_options(opts, options, [:json, :dry_run, :remote])
|
101
|
+
opts.footer = "Get details about a price set.\n" +
|
102
|
+
"[price-set] is required. Price set 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_set_id, options = {})
|
113
|
+
params = {}
|
114
|
+
begin
|
115
|
+
@price_sets_interface.setopts(options)
|
116
|
+
|
117
|
+
if !(price_set_id.to_s =~ /\A\d{1,}\Z/)
|
118
|
+
price_set = find_price_set(price_set_id)
|
119
|
+
|
120
|
+
if !price_set
|
121
|
+
print_red_alert "Price set #{price_set_id} not found"
|
122
|
+
exit 1
|
123
|
+
end
|
124
|
+
price_set_id = price_set['id']
|
125
|
+
end
|
126
|
+
|
127
|
+
if options[:dry_run]
|
128
|
+
print_dry_run @price_sets_interface.dry.get(price_set_id)
|
129
|
+
return
|
130
|
+
end
|
131
|
+
json_response = @price_sets_interface.get(price_set_id)
|
132
|
+
|
133
|
+
render_result = render_with_format(json_response, options, 'priceSet')
|
134
|
+
return 0 if render_result
|
135
|
+
|
136
|
+
title = "Morpheus Price Set"
|
137
|
+
subtitles = []
|
138
|
+
subtitles += parse_list_subtitles(options)
|
139
|
+
print_h1 title, subtitles
|
140
|
+
|
141
|
+
price_set = json_response['priceSet']
|
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
|
+
"Region Code" => lambda {|it| it['regionCode']},
|
148
|
+
"Price Unit" => lambda {|it| (it['priceUnit'] || 'month').capitalize},
|
149
|
+
"Type" => lambda {|it| price_set_type_label(it['type'])},
|
150
|
+
"Cloud" => lambda {|it| it['zone'].nil? ? 'All' : it['zone']['name']},
|
151
|
+
"Resource Pool" => lambda {|it| it['zonePool'].nil? ? nil : it['zonePool']['name']}
|
152
|
+
}
|
153
|
+
|
154
|
+
print_description_list(description_cols, price_set)
|
155
|
+
|
156
|
+
print_h2 "Prices"
|
157
|
+
prices = price_set['prices']
|
158
|
+
|
159
|
+
if prices && !prices.empty?
|
160
|
+
rows = prices.collect do |it|
|
161
|
+
{
|
162
|
+
id: it['id'],
|
163
|
+
name: it['name'],
|
164
|
+
pricing: (it['priceType'] == 'platform' ? '+' : '') + currency_sym(it['currency']) + (it['price'] || 0.0).to_s + (it['additionalPriceUnit'].nil? ? '' : '/' + it['additionalPriceUnit']) + '/' + (it['priceUnit'] || 'month').capitalize
|
165
|
+
}
|
166
|
+
end
|
167
|
+
print as_pretty_table(rows, [:id, :name, :pricing], options)
|
168
|
+
else
|
169
|
+
print yellow,"No prices.",reset,"\n"
|
170
|
+
end
|
171
|
+
print reset,"\n"
|
172
|
+
return 0
|
173
|
+
rescue RestClient::Exception => e
|
174
|
+
print_rest_exception(e, options)
|
175
|
+
exit 1
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def add(args)
|
180
|
+
options = {}
|
181
|
+
params = {}
|
182
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
183
|
+
opts.banner = subcommand_usage()
|
184
|
+
opts.on("--name NAME", String, "Price set name") do |val|
|
185
|
+
params['name'] = val.to_s
|
186
|
+
end
|
187
|
+
opts.on("--code CODE", String, "Price set code, unique identifier") do |val|
|
188
|
+
params['code'] = val.to_s
|
189
|
+
end
|
190
|
+
opts.on("--region-code CODE", String, "Price set region code") do |val|
|
191
|
+
params['regionCode'] = val.to_s
|
192
|
+
end
|
193
|
+
opts.on("--cloud [CLOUD]", String, "Cloud ID or name") do |val|
|
194
|
+
options[:cloud] = val
|
195
|
+
end
|
196
|
+
opts.on("--resource-pool [POOL]", String, "Resource pool ID or name") do |val|
|
197
|
+
options[:resourcePool] = val
|
198
|
+
end
|
199
|
+
opts.on("--price-unit [UNIT]", String, "Price unit") do |val|
|
200
|
+
if price_units.include?(val)
|
201
|
+
params['priceUnit'] = val
|
202
|
+
else
|
203
|
+
raise_command_error "Unrecognized price unit '#{val}'\n- Available price units -\n#{price_units.join("\n")}"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
opts.on('-t', "--type [TYPE]", String, "Price set type") do |val|
|
207
|
+
if ['fixed', 'compute_plus_storage', 'component'].include?(val)
|
208
|
+
params['type'] = val
|
209
|
+
else
|
210
|
+
raise_command_error "Unrecognized price set type #{val}"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
opts.on('--prices [LIST]', Array, 'Price(s), comma separated list of price IDs') do |list|
|
214
|
+
params['prices'] = list.collect {|it| it.to_s.strip.empty? || !it.to_i ? nil : it.to_s.strip}.compact.uniq.collect {|it| {'id' => it.to_i}}
|
215
|
+
end
|
216
|
+
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote, :quiet])
|
217
|
+
opts.footer = "Create price set.\n" +
|
218
|
+
"Name, code, type and price unit are required."
|
219
|
+
end
|
220
|
+
optparse.parse!(args)
|
221
|
+
connect(options)
|
222
|
+
if args.count != 0
|
223
|
+
raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
|
224
|
+
return 1
|
225
|
+
end
|
226
|
+
|
227
|
+
begin
|
228
|
+
payload = parse_payload(options)
|
229
|
+
|
230
|
+
if !payload
|
231
|
+
# name
|
232
|
+
params['name'] ||= 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']
|
233
|
+
|
234
|
+
# code
|
235
|
+
params['code'] ||= 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']
|
236
|
+
|
237
|
+
# region code
|
238
|
+
params['regionCode'] ||= Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'code', 'type' => 'text', 'fieldLabel' => 'Price Set Region Code', 'required' => false, 'description' => 'Price Set Region Code.'}],options[:options],@api_client,{}, options[:no_prompt])['code']
|
239
|
+
|
240
|
+
# cloud
|
241
|
+
if options[:cloud]
|
242
|
+
cloud = find_cloud(options[:cloud])
|
243
|
+
|
244
|
+
if cloud.nil?
|
245
|
+
print_red_alert "Cloud #{options[:cloud]} not found"
|
246
|
+
exit 1
|
247
|
+
end
|
248
|
+
params['zone'] = {'id' => cloud['id']}
|
249
|
+
else
|
250
|
+
cloud_id = Morpheus::Cli::OptionTypes.prompt(['fieldName' => 'value', 'type' => 'select', 'fieldLabel' => 'Cloud', 'required' => false, 'description' => 'Select cloud for price set', 'selectOptions' => @clouds_interface.list['zones'].collect {|it| {'name' => it['name'], 'value' => it['id']}}], options[:options], @api_client, {}, options[:no_prompt], true)['value']
|
251
|
+
|
252
|
+
if cloud_id
|
253
|
+
params['zone'] = {'id' => cloud_id}
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# resource pool
|
258
|
+
if options[:resourcePool]
|
259
|
+
resource_pool = find_resource_pool(params['zone'].nil? ? nil : params['zone']['id'], options[:resourcePool])
|
260
|
+
|
261
|
+
if resource_pool.nil?
|
262
|
+
print_red_alert "Resource pool #{options[:resourcePool]} not found"
|
263
|
+
exit 1
|
264
|
+
end
|
265
|
+
params['zonePool'] = {'id' => resource_pool['id']}
|
266
|
+
else
|
267
|
+
resource_pool_id = Morpheus::Cli::OptionTypes.prompt(['fieldName' => 'value', 'type' => 'select', 'fieldLabel' => 'Resource Pool', 'required' => false, 'description' => 'Select resource pool for price set', 'selectOptions' => @cloud_resource_pools_interface.list(params['zone'] ? params['zone']['id'] : nil)['resourcePools'].collect {|it| {'name' => it['name'], 'value' => it['id']}}], options[:options], @api_client, {}, options[:no_prompt], true)['value']
|
268
|
+
|
269
|
+
if resource_pool_id
|
270
|
+
params['zonePool'] = {'id' => resource_pool_id}
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# price unit
|
275
|
+
params['priceUnit'] ||= Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'priceUnit', 'type' => 'select', 'fieldLabel' => 'Price Unit', 'selectOptions' => price_units.collect {|it| {'name' => it.split(' ').collect {|it| it.capitalize}.join(' '), 'value' => it}}, 'required' => true, 'description' => 'Price Unit.', 'defaultValue' => 'month'}],options[:options],@api_client,{}, options[:no_prompt])['priceUnit']
|
276
|
+
if params['priceUnit'].nil?
|
277
|
+
print_red_alert "Price unit is required"
|
278
|
+
exit 1
|
279
|
+
end
|
280
|
+
|
281
|
+
# type
|
282
|
+
params['type'] ||= Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'type' => 'select', 'fieldLabel' => 'Price Set Type', 'selectOptions' => [{'name' => 'Everything', 'value' => 'fixed'}, {'name' => 'Compute + Storage', 'value' => 'compute_plus_storage'}, {'name' => 'Component', 'value' => 'component'}], 'required' => true, 'description' => 'Price Set Type.'}],options[:options],@api_client,{}, options[:no_prompt])['type']
|
283
|
+
if params['type'].nil?
|
284
|
+
print_red_alert "Type is required"
|
285
|
+
exit 1
|
286
|
+
end
|
287
|
+
|
288
|
+
# required prices
|
289
|
+
price_set_type = price_set_types[params['type']]
|
290
|
+
prices = params['prices'] ? @prices_interface.list({'ids' => params['prices'].collect {|it| it['id']}})['prices'] : []
|
291
|
+
required = price_set_type[:requires].reject {|it| prices.find {|price| price['priceType'] == it}}
|
292
|
+
|
293
|
+
if !options[:no_prompt]
|
294
|
+
params['prices'] ||= []
|
295
|
+
while required.count > 0 do
|
296
|
+
price_type = required.pop
|
297
|
+
avail_prices = @prices_interface.list({'priceType' => price_type, 'priceUnit' => params['priceUnit'], 'max' => 10000})['prices'].reject {|it| params['prices'].find {|price| price['id'] == it['id']}}.collect {|it| {'name' => it['name'], 'value' => it['id']}}
|
298
|
+
|
299
|
+
if avail_prices.count > 0
|
300
|
+
price_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'price', 'type' => 'select', 'fieldLabel' => "Add #{price_type_label(price_type)} Price", 'selectOptions' => avail_prices, 'required' => true, 'description' => "'#{price_set_type[:label]}' price sets require 1 or more '#{price_type_label(price_type)}' price types"}],options[:options],@api_client,{}, options[:no_prompt], true)['price']
|
301
|
+
params['prices'] << {'id' => price_id}
|
302
|
+
else
|
303
|
+
print_red_alert "'#{price_set_type[:label]}' price sets require 1 or more '#{price_type_label(price_type)}' price types, however there are none available for the #{params['priceUnit']} price unit."
|
304
|
+
exit 1
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
# additional prices
|
309
|
+
avail_price_types = (price_set_type[:requires] + price_set_type[:allows]).collect {|it| {'name' => price_type_label(it), 'value' => it}}
|
310
|
+
price_type = nil
|
311
|
+
while Morpheus::Cli::OptionTypes.confirm("Add additional prices?") do
|
312
|
+
price_type = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'priceType', 'type' => 'select', 'fieldLabel' => "Price Type", 'selectOptions' => avail_price_types, 'required' => true, 'defaultValue' => price_type, 'description' => "Select Price Type"}],options[:options],@api_client,{}, options[:no_prompt], true)['priceType']
|
313
|
+
avail_prices = @prices_interface.list({'priceType' => price_type, 'priceUnit' => params['priceUnit'], 'max' => 10000})['prices'].reject {|it| params['prices'].find {|price| price['id'] == it['id']}}.collect {|it| {'name' => it['name'], 'value' => it['id']}}
|
314
|
+
|
315
|
+
if avail_prices.count > 0
|
316
|
+
price_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'price', 'type' => 'select', 'fieldLabel' => "Add #{price_type_label(price_type)} Price", 'selectOptions' => avail_prices, 'required' => true, 'description' => "Add #{price_type_label(price_type)} Price"}],options[:options],@api_client,{}, options[:no_prompt], true)['price']
|
317
|
+
params['prices'] << {'id' => price_id}
|
318
|
+
else
|
319
|
+
print_red_alert "No available prices for '#{price_type}'"
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
payload = {'priceSet' => params}
|
324
|
+
end
|
325
|
+
|
326
|
+
@price_sets_interface.setopts(options)
|
327
|
+
if options[:dry_run]
|
328
|
+
print_dry_run @price_sets_interface.dry.create(payload)
|
329
|
+
return
|
330
|
+
end
|
331
|
+
json_response = @price_sets_interface.create(payload)
|
332
|
+
|
333
|
+
if options[:json]
|
334
|
+
puts as_json(json_response, options)
|
335
|
+
elsif !options[:quiet]
|
336
|
+
if json_response['success']
|
337
|
+
print_green_success "Price set created"
|
338
|
+
_get(json_response['id'], options)
|
339
|
+
else
|
340
|
+
print_red_alert "Error creating price set: #{json_response['msg'] || json_response['errors']}"
|
341
|
+
end
|
342
|
+
end
|
343
|
+
return 0
|
344
|
+
rescue RestClient::Exception => e
|
345
|
+
print_rest_exception(e, options)
|
346
|
+
exit 1
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def update(args)
|
351
|
+
options = {}
|
352
|
+
params = {}
|
353
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
354
|
+
opts.banner = subcommand_usage("[price-set]")
|
355
|
+
opts.on("--name NAME", String, "Price set name") do |val|
|
356
|
+
params['name'] = val.to_s
|
357
|
+
end
|
358
|
+
opts.on('--restart-usage [on|off]', String, "Apply price changes to usage. Default is on") do |val|
|
359
|
+
params['restartUsage'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
|
360
|
+
end
|
361
|
+
opts.on('--prices [LIST]', Array, 'Price(s), comma separated list of price IDs') do |list|
|
362
|
+
params['prices'] = list.collect {|it| it.to_s.strip.empty? || !it.to_i ? nil : it.to_s.strip}.compact.uniq.collect {|it| {'id' => it.to_i}}
|
363
|
+
end
|
364
|
+
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote, :quiet])
|
365
|
+
opts.footer = "Update price set.\n" +
|
366
|
+
"[price-set] is required. Price set ID, name or code"
|
367
|
+
end
|
368
|
+
optparse.parse!(args)
|
369
|
+
connect(options)
|
370
|
+
if args.count != 1
|
371
|
+
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
|
372
|
+
return 1
|
373
|
+
end
|
374
|
+
|
375
|
+
begin
|
376
|
+
price_set = find_price_set(args[0])
|
377
|
+
|
378
|
+
if price_set.nil?
|
379
|
+
print_red_alert "Price set #{args[0]} not found"
|
380
|
+
exit 1
|
381
|
+
end
|
382
|
+
|
383
|
+
payload = parse_payload(options)
|
384
|
+
|
385
|
+
if payload.nil?
|
386
|
+
payload = {'priceSet' => params}
|
387
|
+
end
|
388
|
+
|
389
|
+
if payload['priceSet'].empty?
|
390
|
+
print_green_success "Nothing to update"
|
391
|
+
return
|
392
|
+
end
|
393
|
+
|
394
|
+
@price_sets_interface.setopts(options)
|
395
|
+
if options[:dry_run]
|
396
|
+
print_dry_run @price_sets_interface.dry.update(price_set['id'], payload)
|
397
|
+
return
|
398
|
+
end
|
399
|
+
json_response = @price_sets_interface.update(price_set['id'], payload)
|
400
|
+
|
401
|
+
if options[:json]
|
402
|
+
puts as_json(json_response, options)
|
403
|
+
elsif !options[:quiet]
|
404
|
+
if json_response['success']
|
405
|
+
print_green_success "Price set updated"
|
406
|
+
_get(price_set['id'], options)
|
407
|
+
else
|
408
|
+
print_red_alert "Error updating price set: #{json_response['msg'] || json_response['errors']}"
|
409
|
+
end
|
410
|
+
end
|
411
|
+
return 0
|
412
|
+
rescue RestClient::Exception => e
|
413
|
+
print_rest_exception(e, options)
|
414
|
+
exit 1
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
def deactivate(args)
|
419
|
+
options = {}
|
420
|
+
params = {}
|
421
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
422
|
+
opts.banner = subcommand_usage( "[price-set]")
|
423
|
+
build_common_options(opts, options, [:json, :dry_run, :remote])
|
424
|
+
opts.footer = "Deactivate price set.\n" +
|
425
|
+
"[price-set] is required. Price set ID, name or code"
|
426
|
+
end
|
427
|
+
optparse.parse!(args)
|
428
|
+
connect(options)
|
429
|
+
if args.count != 1
|
430
|
+
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
|
431
|
+
return 1
|
432
|
+
end
|
433
|
+
|
434
|
+
begin
|
435
|
+
price_set = find_price_set(args[0])
|
436
|
+
|
437
|
+
if !price_set
|
438
|
+
print_red_alert "Price set #{args[0]} not found"
|
439
|
+
exit 1
|
440
|
+
end
|
441
|
+
|
442
|
+
if price_set['active'] == false
|
443
|
+
print_green_success "Price set #{price_set['name']} already deactived."
|
444
|
+
return 0
|
445
|
+
end
|
446
|
+
|
447
|
+
unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to deactivate the price set '#{price_set['name']}'?", options)
|
448
|
+
return 9, "aborted command"
|
449
|
+
end
|
450
|
+
|
451
|
+
@price_sets_interface.setopts(options)
|
452
|
+
if options[:dry_run]
|
453
|
+
print_dry_run @price_sets_interface.dry.deactivate(price_set['id'], params)
|
454
|
+
return
|
455
|
+
end
|
456
|
+
|
457
|
+
json_response = @price_sets_interface.deactivate(price_set['id'], params)
|
458
|
+
|
459
|
+
if options[:json]
|
460
|
+
print JSON.pretty_generate(json_response)
|
461
|
+
print "\n"
|
462
|
+
elsif !options[:quiet]
|
463
|
+
print_green_success "Price set #{price_set['name']} deactivate"
|
464
|
+
end
|
465
|
+
return 0
|
466
|
+
rescue RestClient::Exception => e
|
467
|
+
print_rest_exception(e, options)
|
468
|
+
exit 1
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
private
|
473
|
+
|
474
|
+
def currency_sym(currency)
|
475
|
+
Money::Currency.new((currency || 'usd').to_sym).symbol
|
476
|
+
end
|
477
|
+
|
478
|
+
def price_prefix(price)
|
479
|
+
(['platform', 'software'].include?(price['priceType']) ? '+' : '') + currency_sym(price['currency'])
|
480
|
+
end
|
481
|
+
|
482
|
+
def price_markup(price)
|
483
|
+
if price['markupType'] == 'fixed'
|
484
|
+
currency_sym(price['currency']) + format_amount(price['markup'] || 0)
|
485
|
+
elsif price['markupType'] == 'percent'
|
486
|
+
(price['markupPercent'] || 0).to_s + '%'
|
487
|
+
else
|
488
|
+
'N/A'
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
def find_price_set(val)
|
493
|
+
(val.to_s =~ /\A\d{1,}\Z/) ? @price_sets_interface.get(val.to_i)['priceSet'] : @price_sets_interface.list({'code' => val, 'name' => val})["priceSets"].first
|
494
|
+
end
|
495
|
+
|
496
|
+
def find_cloud(val)
|
497
|
+
(val.to_s =~ /\A\d{1,}\Z/) ? @clouds_interface.get(val.to_i)['zone'] : @clouds_interface.list({'name' => val})["zones"].first
|
498
|
+
end
|
499
|
+
|
500
|
+
def find_resource_pool(cloud_id, val)
|
501
|
+
(val.to_s =~ /\A\d{1,}\Z/) ? @cloud_resource_pools_interface.get(cloud_id, val.to_i)['resourcePool'] : @cloud_resource_pools_interface.list(cloud_id, {'name' => val})["resourcePools"].first
|
502
|
+
end
|
503
|
+
|
504
|
+
def price_set_type_label(type)
|
505
|
+
price_set_types[type][:label]
|
506
|
+
end
|
507
|
+
|
508
|
+
def price_type_label(type)
|
509
|
+
{
|
510
|
+
'fixed' => 'Everything',
|
511
|
+
'compute' => 'Memory + CPU',
|
512
|
+
'memory' => 'Memory Only (per MB)',
|
513
|
+
'cores' => 'Cores Only (per core)',
|
514
|
+
'storage' => 'Disk Only (per GB)',
|
515
|
+
'datastore' => 'Datastore (per GB)',
|
516
|
+
'platform' => 'Platform',
|
517
|
+
'software' => 'Software'
|
518
|
+
}[type] || type.capitalize
|
519
|
+
end
|
520
|
+
|
521
|
+
def price_units
|
522
|
+
['minute', 'hour', 'day', 'month', 'year', 'two year', 'three year', 'four year', 'five year']
|
523
|
+
end
|
524
|
+
|
525
|
+
def price_set_types
|
526
|
+
{
|
527
|
+
'fixed' => {:label => 'Everything', :requires => ['fixed'], :allows => ['platform', 'software']},
|
528
|
+
'compute_plus_storage' => {:label => 'Compute + Storage', :requires => ['compute', 'storage'], :allows => ['platform', 'software']},
|
529
|
+
'component' => {:label => 'Component', :requires => ['memory', 'cores', 'storage'], :allows => ['platform', 'software']},
|
530
|
+
}
|
531
|
+
end
|
532
|
+
|
533
|
+
def format_amount(amount)
|
534
|
+
rtn = amount.to_s
|
535
|
+
if rtn.index('.').nil?
|
536
|
+
rtn += '.00'
|
537
|
+
elsif rtn.split('.')[1].length < 2
|
538
|
+
print rtn.split('.')[1].length
|
539
|
+
rtn = rtn + (['0'] * (2 - rtn.split('.')[1].length) * '')
|
540
|
+
end
|
541
|
+
rtn
|
542
|
+
end
|
543
|
+
end
|