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.
- 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
@@ -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
|