morpheus-cli 5.0.2 → 5.2.0
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 +4 -0
- data/lib/morpheus/api/instances_interface.rb +9 -2
- data/lib/morpheus/api/servers_interface.rb +7 -0
- data/lib/morpheus/api/service_catalog_interface.rb +89 -0
- data/lib/morpheus/cli.rb +2 -1
- data/lib/morpheus/cli/apps.rb +3 -23
- data/lib/morpheus/cli/{catalog_command.rb → catalog_item_types_command.rb} +182 -67
- data/lib/morpheus/cli/cli_command.rb +25 -1
- data/lib/morpheus/cli/containers_command.rb +0 -24
- data/lib/morpheus/cli/cypher_command.rb +6 -2
- data/lib/morpheus/cli/health_command.rb +57 -0
- data/lib/morpheus/cli/hosts.rb +85 -9
- data/lib/morpheus/cli/instances.rb +85 -68
- data/lib/morpheus/cli/library_option_lists_command.rb +1 -1
- data/lib/morpheus/cli/library_option_types_command.rb +5 -2
- data/lib/morpheus/cli/mixins/accounts_helper.rb +5 -1
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +76 -0
- data/lib/morpheus/cli/option_types.rb +10 -10
- data/lib/morpheus/cli/roles.rb +193 -155
- data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
- data/lib/morpheus/cli/tasks.rb +9 -11
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/virtual_images.rb +162 -68
- data/lib/morpheus/formatters.rb +34 -9
- metadata +5 -4
- data/lib/morpheus/cli/mixins/catalog_helper.rb +0 -66
@@ -0,0 +1,1474 @@
|
|
1
|
+
require 'morpheus/cli/cli_command'
|
2
|
+
|
3
|
+
# CLI command for the Service Catalog (Persona): Dashboard / Catalog / Inventory
|
4
|
+
# Inventory Items are the main actions, list, get, remove
|
5
|
+
# The add command adds to the cart and checkout places an order with the cart.
|
6
|
+
# The add-order command allows submitting a new order at once.
|
7
|
+
class Morpheus::Cli::CatalogCommand
|
8
|
+
include Morpheus::Cli::CliCommand
|
9
|
+
include Morpheus::Cli::ProvisioningHelper
|
10
|
+
include Morpheus::Cli::OptionSourceHelper
|
11
|
+
|
12
|
+
# set_command_name :'service-catalog'
|
13
|
+
set_command_name :'catalog'
|
14
|
+
set_command_description "Service Catalog Persona: View catalog and manage inventory"
|
15
|
+
|
16
|
+
# dashboard
|
17
|
+
register_subcommands :dashboard
|
18
|
+
# catalog (catalogItemTypes)
|
19
|
+
register_subcommands :'list-types' => :list_types
|
20
|
+
register_subcommands :'get-type' => :get_type
|
21
|
+
alias_subcommand :types, :'list-types'
|
22
|
+
|
23
|
+
# inventory (items) IS the main crud here
|
24
|
+
register_subcommands :list, :get, :remove
|
25
|
+
|
26
|
+
# cart / orders
|
27
|
+
register_subcommands :cart => :get_cart
|
28
|
+
register_subcommands :'update-cart' => :update_cart
|
29
|
+
register_subcommands :add
|
30
|
+
#register_subcommands :'update-cart-item' => :update_cart_item
|
31
|
+
register_subcommands :'remove-cart-item' => :remove_cart_item
|
32
|
+
register_subcommands :'clear-cart' => :clear_cart
|
33
|
+
register_subcommands :checkout
|
34
|
+
|
35
|
+
# create and submit cart in one action
|
36
|
+
# maybe call this place-order instead?
|
37
|
+
register_subcommands :'add-order' => :add_order
|
38
|
+
|
39
|
+
def default_sigdig
|
40
|
+
4
|
41
|
+
end
|
42
|
+
|
43
|
+
def connect(opts)
|
44
|
+
@api_client = establish_remote_appliance_connection(opts)
|
45
|
+
@service_catalog_interface = @api_client.catalog
|
46
|
+
@instances_interface = @api_client.instances
|
47
|
+
@servers_interface = @api_client.servers # should not be required here!
|
48
|
+
@option_types_interface = @api_client.option_types
|
49
|
+
end
|
50
|
+
|
51
|
+
def handle(args)
|
52
|
+
handle_subcommand(args)
|
53
|
+
end
|
54
|
+
|
55
|
+
def dashboard(args)
|
56
|
+
params = {}
|
57
|
+
options = {}
|
58
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
59
|
+
opts.banner = subcommand_usage()
|
60
|
+
opts.add_hidden_option('--sigdig')
|
61
|
+
build_standard_get_options(opts, options, [:sigdig] || default_sigdig)
|
62
|
+
opts.footer = <<-EOT
|
63
|
+
View service catalog dashboard.
|
64
|
+
Provides an overview of available catalog item types, recent orders and inventory.
|
65
|
+
EOT
|
66
|
+
end
|
67
|
+
optparse.parse!(args)
|
68
|
+
verify_args!(args:args, optparse:optparse, max:0)
|
69
|
+
connect(options)
|
70
|
+
|
71
|
+
params.merge!(parse_list_options(options))
|
72
|
+
@service_catalog_interface.setopts(options)
|
73
|
+
if options[:dry_run]
|
74
|
+
print_dry_run @service_catalog_interface.dry.dashboard(params)
|
75
|
+
return
|
76
|
+
end
|
77
|
+
json_response = @service_catalog_interface.dashboard(params)
|
78
|
+
catalog_item_types = json_response['catalogItemTypes']
|
79
|
+
catalog_meta = json_response['catalogMeta'] || {}
|
80
|
+
recent_items = json_response['recentItems'] || {}
|
81
|
+
featured_items = json_response['featuredItems'] || []
|
82
|
+
inventory_items = json_response['inventoryItems'] || []
|
83
|
+
inventory_meta = json_response['inventoryMeta'] || {}
|
84
|
+
cart = json_response['cart'] || {}
|
85
|
+
cart_items = cart['items'] || []
|
86
|
+
cart_stats = cart['stats'] || {}
|
87
|
+
current_invoice = json_response['currentInvoice']
|
88
|
+
|
89
|
+
render_response(json_response, options, catalog_item_type_object_key) do
|
90
|
+
print_h1 "Catalog Dashboard", [], options
|
91
|
+
print cyan
|
92
|
+
|
93
|
+
# dashboard_columns = [
|
94
|
+
# {"TYPES" => lambda {|it| catalog_meta['total'] } },
|
95
|
+
# {"INVENTORY" => lambda {|it| inventory_items.size rescue '' } },
|
96
|
+
# {"CART" => lambda {|it| it['cart']['items'].size rescue '' } },
|
97
|
+
# ]
|
98
|
+
# print as_pretty_table([json_response], dashboard_columns, options)
|
99
|
+
|
100
|
+
print_h2 "Catalog Items"
|
101
|
+
print as_pretty_table(catalog_item_types, {
|
102
|
+
"NAME" => lambda {|it| it['name'] },
|
103
|
+
"DESCRIPTION" => lambda {|it| it['description'] },
|
104
|
+
"FEATURED" => lambda {|it| format_boolean it['featured'] },
|
105
|
+
}, options)
|
106
|
+
# print reset,"\n"
|
107
|
+
|
108
|
+
if recent_items && recent_items.size() > 0
|
109
|
+
print_h2 "Recently Ordered"
|
110
|
+
print as_pretty_table(recent_items, {
|
111
|
+
#"ID" => lambda {|it| it['id'] },
|
112
|
+
#"NAME" => lambda {|it| it['name'] },
|
113
|
+
"TYPE" => lambda {|it| it['type']['name'] rescue '' },
|
114
|
+
#"QTY" => lambda {|it| it['quantity'] },
|
115
|
+
"ORDER DATE" => lambda {|it| format_local_dt(it['orderDate']) },
|
116
|
+
# "STATUS" => lambda {|it| format_catalog_item_status(it) },
|
117
|
+
# "CONFIG" => lambda {|it| truncate_string(format_name_values(it['config']), 50) },
|
118
|
+
}, options)
|
119
|
+
# print reset,"\n"
|
120
|
+
end
|
121
|
+
|
122
|
+
if recent_items && recent_items.size() > 0
|
123
|
+
print_h2 "Inventory"
|
124
|
+
print as_pretty_table(inventory_items, {
|
125
|
+
"ID" => lambda {|it| it['id'] },
|
126
|
+
"NAME" => lambda {|it| it['name'] },
|
127
|
+
"TYPE" => lambda {|it| it['type']['name'] rescue '' },
|
128
|
+
#"QTY" => lambda {|it| it['quantity'] },
|
129
|
+
"ORDER DATE" => lambda {|it| format_local_dt(it['orderDate']) },
|
130
|
+
"STATUS" => lambda {|it| format_catalog_item_status(it) },
|
131
|
+
# "CONFIG" => lambda {|it| format_name_values(it['config']) },
|
132
|
+
}, options)
|
133
|
+
print_results_pagination(inventory_meta)
|
134
|
+
else
|
135
|
+
# print_h2 "Inventory"
|
136
|
+
# print cyan, "Inventory is empty", reset, "\n"
|
137
|
+
end
|
138
|
+
|
139
|
+
# print reset,"\n"
|
140
|
+
|
141
|
+
# problematic right now, invoice has all user activity, not just catalog
|
142
|
+
show_invoice = false
|
143
|
+
if current_invoice && show_invoice
|
144
|
+
print_h2 "Current Invoice"
|
145
|
+
print cyan
|
146
|
+
invoice_columns = {
|
147
|
+
# todo: invoice needs to return a currency!!!
|
148
|
+
"Compute" => lambda {|it| format_money(it['computePrice'], cart_stats['currency'], {sigdig:options[:sigdig] || default_sigdig}) },
|
149
|
+
"Storage" => lambda {|it| format_money(it['storagePrice'], cart_stats['currency'], {sigdig:options[:sigdig] || default_sigdig}) },
|
150
|
+
"Memory" => lambda {|it| format_money(it['memoryPrice'], cart_stats['currency'], {sigdig:options[:sigdig] || default_sigdig}) },
|
151
|
+
"Network" => lambda {|it| format_money(it['networkPrice'], cart_stats['currency'], {sigdig:options[:sigdig] || default_sigdig}) },
|
152
|
+
"Extra" => lambda {|it| format_money(it['extraPrice'], cart_stats['currency'], {sigdig:options[:sigdig] || default_sigdig}) },
|
153
|
+
"MTD" => lambda {|it| format_money(it['runningPrice'], cart_stats['currency'], {sigdig:options[:sigdig] || default_sigdig}) },
|
154
|
+
"Total (Projected)" => lambda {|it| format_money(it['totalPrice'], cart_stats['currency'], {sigdig:options[:sigdig] || default_sigdig}) },
|
155
|
+
#"Items" => lambda {|it| cart['items'].size },
|
156
|
+
# "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
157
|
+
# "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
|
158
|
+
}
|
159
|
+
invoice_columns.delete("Storage") unless current_invoice['storagePrice'] && current_invoice['storagePrice'].to_f > 0
|
160
|
+
invoice_columns.delete("Memory") unless current_invoice['memoryPrice'] && current_invoice['memoryPrice'].to_f > 0
|
161
|
+
invoice_columns.delete("Network") unless current_invoice['networkPrice'] && current_invoice['networkPrice'].to_f > 0
|
162
|
+
invoice_columns.delete("Extra") unless current_invoice['extraPrice'] && current_invoice['extraPrice'].to_f > 0
|
163
|
+
print as_pretty_table(current_invoice, invoice_columns.upcase_keys!, options)
|
164
|
+
end
|
165
|
+
|
166
|
+
show_cart = cart && cart['items'] && cart['items'].size() > 0
|
167
|
+
if show_cart
|
168
|
+
if cart
|
169
|
+
|
170
|
+
# get_cart([] + (options[:remote] ? ["-r",options[:remote]] : []))
|
171
|
+
|
172
|
+
print_h2 "Cart"
|
173
|
+
print cyan
|
174
|
+
if cart['items'].size() > 0
|
175
|
+
# cart_columns = {
|
176
|
+
# "Qty" => lambda {|it| cart['items'].sum {|cart_item| cart_item['quantity'] } },
|
177
|
+
# "Total" => lambda {|it|
|
178
|
+
# begin
|
179
|
+
# format_money(cart_stats['price'], cart_stats['currency'], {sigdig:options[:sigdig] || default_sigdig}) + (cart_stats['unit'].to_s.empty? ? "" : " / #{cart_stats['unit'] || 'month'}")
|
180
|
+
# rescue => ex
|
181
|
+
# raise ex
|
182
|
+
# # no cart stats eh?
|
183
|
+
# end
|
184
|
+
# },
|
185
|
+
# }
|
186
|
+
# print as_pretty_table(cart, cart_columns.upcase_keys!, options)
|
187
|
+
|
188
|
+
|
189
|
+
cart_item_columns = [
|
190
|
+
{"ID" => lambda {|it| it['id'] } },
|
191
|
+
#{"NAME" => lambda {|it| it['name'] } },
|
192
|
+
{"TYPE" => lambda {|it| it['type']['name'] rescue '' } },
|
193
|
+
#{"QTY" => lambda {|it| it['quantity'] } },
|
194
|
+
{"PRICE" => lambda {|it| it['price'] ? format_money(it['price'] , it['currency'], {sigdig:options[:sigdig] || default_sigdig}) : "No pricing configured" } },
|
195
|
+
{"STATUS" => lambda {|it|
|
196
|
+
status_string = format_catalog_item_status(it)
|
197
|
+
if it['errorMessage'].to_s != ""
|
198
|
+
status_string << " - #{it['errorMessage']}"
|
199
|
+
end
|
200
|
+
status_string
|
201
|
+
} },
|
202
|
+
# {"CONFIG" => lambda {|it|
|
203
|
+
# truncate_string(format_name_values(it['config']), 50)
|
204
|
+
# } },
|
205
|
+
]
|
206
|
+
print as_pretty_table(cart_items, cart_item_columns)
|
207
|
+
|
208
|
+
print reset,"\n"
|
209
|
+
print cyan
|
210
|
+
if cart_stats['price']
|
211
|
+
puts "Total: " + format_money(cart_stats['price'], cart_stats['currency'], {sigdig:options[:sigdig] || default_sigdig}) + " / #{cart_stats['unit'].to_s.empty? ? 'month' : cart_stats['unit']}"
|
212
|
+
else
|
213
|
+
puts "Total: " + "No pricing configured"
|
214
|
+
end
|
215
|
+
# print reset,"\n"
|
216
|
+
|
217
|
+
else
|
218
|
+
print cyan, "Cart is empty", reset, "\n"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
print reset,"\n"
|
225
|
+
|
226
|
+
end
|
227
|
+
return 0, nil
|
228
|
+
end
|
229
|
+
|
230
|
+
def list_types(args)
|
231
|
+
options = {}
|
232
|
+
params = {}
|
233
|
+
ref_ids = []
|
234
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
235
|
+
opts.banner = subcommand_usage("[search]")
|
236
|
+
opts.on( '--featured [on|off]', String, "Filter by featured" ) do |val|
|
237
|
+
params['featured'] = (val.to_s != 'false' && val.to_s != 'off')
|
238
|
+
end
|
239
|
+
build_standard_list_options(opts, options, [:sigdig])
|
240
|
+
opts.footer = "List available catalog item types."
|
241
|
+
end
|
242
|
+
optparse.parse!(args)
|
243
|
+
connect(options)
|
244
|
+
# verify_args!(args:args, optparse:optparse, count:0)
|
245
|
+
if args.count > 0
|
246
|
+
options[:phrase] = args.join(" ")
|
247
|
+
end
|
248
|
+
params.merge!(parse_list_options(options))
|
249
|
+
@service_catalog_interface.setopts(options)
|
250
|
+
if options[:dry_run]
|
251
|
+
print_dry_run @service_catalog_interface.dry.list_types(params)
|
252
|
+
return
|
253
|
+
end
|
254
|
+
json_response = @service_catalog_interface.list_types(params)
|
255
|
+
catalog_item_types = json_response[catalog_item_type_list_key]
|
256
|
+
render_response(json_response, options, catalog_item_type_list_key) do
|
257
|
+
print_h1 "Morpheus Catalog Types", parse_list_subtitles(options), options
|
258
|
+
if catalog_item_types.empty?
|
259
|
+
print cyan,"No catalog item types found.",reset,"\n"
|
260
|
+
else
|
261
|
+
list_columns = catalog_item_type_column_definitions.upcase_keys!
|
262
|
+
#list_columns["Config"] = lambda {|it| truncate_string(it['config'], 100) }
|
263
|
+
print as_pretty_table(catalog_item_types, list_columns.upcase_keys!, options)
|
264
|
+
print_results_pagination(json_response)
|
265
|
+
end
|
266
|
+
print reset,"\n"
|
267
|
+
end
|
268
|
+
if catalog_item_types.empty?
|
269
|
+
return 1, "no catalog item types found"
|
270
|
+
else
|
271
|
+
return 0, nil
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def get_type(args)
|
276
|
+
params = {}
|
277
|
+
options = {}
|
278
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
279
|
+
opts.banner = subcommand_usage("[name]")
|
280
|
+
build_standard_get_options(opts, options, [:sigdig])
|
281
|
+
opts.footer = <<-EOT
|
282
|
+
Get details about a specific catalog item type.
|
283
|
+
[name] is required. This is the name or id of a catalog item type.
|
284
|
+
EOT
|
285
|
+
end
|
286
|
+
optparse.parse!(args)
|
287
|
+
verify_args!(args:args, optparse:optparse, min:1)
|
288
|
+
connect(options)
|
289
|
+
id_list = parse_id_list(args)
|
290
|
+
return run_command_for_each_arg(id_list) do |arg|
|
291
|
+
_get_type(arg, params, options)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def _get_type(id, params, options)
|
296
|
+
catalog_item_type = nil
|
297
|
+
if id.to_s !~ /\A\d{1,}\Z/
|
298
|
+
catalog_item_type = find_catalog_item_type_by_name(id)
|
299
|
+
return 1, "catalog item type not found for #{id}" if catalog_item_type.nil?
|
300
|
+
id = catalog_item_type['id']
|
301
|
+
end
|
302
|
+
@service_catalog_interface.setopts(options)
|
303
|
+
if options[:dry_run]
|
304
|
+
print_dry_run @service_catalog_interface.dry.get_type(id, params)
|
305
|
+
return
|
306
|
+
end
|
307
|
+
json_response = @service_catalog_interface.get_type(id, params)
|
308
|
+
catalog_item_type = json_response[catalog_item_type_object_key]
|
309
|
+
# need to load by id to get optionTypes
|
310
|
+
# maybe do ?name=foo&includeOptionTypes=true
|
311
|
+
if catalog_item_type['optionTypes'].nil?
|
312
|
+
catalog_item_type = find_catalog_item_type_by_id(catalog_item_type['id'])
|
313
|
+
return [1, "catalog item type not found"] if catalog_item_type.nil?
|
314
|
+
end
|
315
|
+
render_response(json_response, options, catalog_item_type_object_key) do
|
316
|
+
print_h1 "Catalog Type Details", [], options
|
317
|
+
print cyan
|
318
|
+
show_columns = catalog_item_type_column_definitions
|
319
|
+
print_description_list(show_columns, catalog_item_type)
|
320
|
+
|
321
|
+
if catalog_item_type['optionTypes'] && catalog_item_type['optionTypes'].size > 0
|
322
|
+
print_h2 "Configuration Options"
|
323
|
+
print as_pretty_table(catalog_item_type['optionTypes'], {
|
324
|
+
"LABEL" => lambda {|it| it['fieldLabel'] },
|
325
|
+
"NAME" => lambda {|it| it['fieldName'] },
|
326
|
+
"TYPE" => lambda {|it| it['type'] },
|
327
|
+
"REQUIRED" => lambda {|it| format_boolean it['required'] },
|
328
|
+
})
|
329
|
+
else
|
330
|
+
# print cyan,"No option types found for this catalog item.","\n",reset
|
331
|
+
end
|
332
|
+
|
333
|
+
print reset,"\n"
|
334
|
+
end
|
335
|
+
return 0, nil
|
336
|
+
end
|
337
|
+
|
338
|
+
# inventory actions
|
339
|
+
|
340
|
+
def list(args)
|
341
|
+
options = {}
|
342
|
+
params = {}
|
343
|
+
ref_ids = []
|
344
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
345
|
+
opts.banner = subcommand_usage("[search]")
|
346
|
+
opts.on('-t', '--type TYPE', String, "Catalog Item Type Name or ID") do |val|
|
347
|
+
type_id = val.to_s
|
348
|
+
end
|
349
|
+
build_standard_list_options(opts, options, [:sigdig])
|
350
|
+
opts.footer = "List catalog inventory."
|
351
|
+
end
|
352
|
+
optparse.parse!(args)
|
353
|
+
connect(options)
|
354
|
+
# verify_args!(args:args, optparse:optparse, count:0)
|
355
|
+
if args.count > 0
|
356
|
+
options[:phrase] = args.join(" ")
|
357
|
+
end
|
358
|
+
params.merge!(parse_list_options(options))
|
359
|
+
@service_catalog_interface.setopts(options)
|
360
|
+
if options[:dry_run]
|
361
|
+
print_dry_run @service_catalog_interface.dry.list_inventory(params)
|
362
|
+
return
|
363
|
+
end
|
364
|
+
json_response = @service_catalog_interface.list_inventory(params)
|
365
|
+
catalog_items = json_response[catalog_item_list_key]
|
366
|
+
render_response(json_response, options, catalog_item_list_key) do
|
367
|
+
print_h1 "Morpheus Catalog Inventory", parse_list_subtitles(options), options
|
368
|
+
if catalog_items.empty?
|
369
|
+
print cyan,"No catalog items found.",reset,"\n"
|
370
|
+
else
|
371
|
+
list_columns = catalog_item_column_definitions.upcase_keys!
|
372
|
+
#list_columns["Config"] = lambda {|it| truncate_string(it['config'], 100) }
|
373
|
+
print as_pretty_table(catalog_items, list_columns.upcase_keys!, options)
|
374
|
+
print_results_pagination(json_response)
|
375
|
+
end
|
376
|
+
print reset,"\n"
|
377
|
+
end
|
378
|
+
if catalog_items.empty?
|
379
|
+
return 1, "no catalog items found"
|
380
|
+
else
|
381
|
+
return 0, nil
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
def get(args)
|
386
|
+
params = {}
|
387
|
+
options = {}
|
388
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
389
|
+
opts.banner = subcommand_usage("[item]")
|
390
|
+
opts.on( '-c', '--config', "Display raw config only. Default is YAML. Combine with -j for JSON instead." ) do
|
391
|
+
options[:show_config] = true
|
392
|
+
end
|
393
|
+
# opts.on('--no-config', "Do not display config content." ) do
|
394
|
+
# options[:no_config] = true
|
395
|
+
# end
|
396
|
+
build_standard_get_options(opts, options, [:sigdig])
|
397
|
+
opts.footer = <<-EOT
|
398
|
+
Get details about a specific catalog inventory item.
|
399
|
+
[item] is required. This is the name or id of a catalog inventory item.
|
400
|
+
EOT
|
401
|
+
end
|
402
|
+
optparse.parse!(args)
|
403
|
+
verify_args!(args:args, optparse:optparse, min:1)
|
404
|
+
connect(options)
|
405
|
+
id_list = parse_id_list(args)
|
406
|
+
return run_command_for_each_arg(id_list) do |arg|
|
407
|
+
_get(arg, params, options)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def _get(id, params, options)
|
412
|
+
catalog_item = find_catalog_item_by_name_or_id(id)
|
413
|
+
return 1, "catalog item not found for name or id '#{id}'" if catalog_item.nil?
|
414
|
+
@service_catalog_interface.setopts(options)
|
415
|
+
if options[:dry_run]
|
416
|
+
print_dry_run @service_catalog_interface.dry.get_inventory(catalog_item['id'], params)
|
417
|
+
return
|
418
|
+
end
|
419
|
+
# skip redundant request
|
420
|
+
json_response = {catalog_item_object_key => catalog_item}
|
421
|
+
# if id !~ /\A\d{1,}\Z/
|
422
|
+
# json_response = @service_catalog_interface.get_inventory(catalog_item['id'], params)
|
423
|
+
# end
|
424
|
+
catalog_item = json_response[catalog_item_object_key]
|
425
|
+
item_config = catalog_item['config']
|
426
|
+
item_type_code = catalog_item['type']['type'].to_s.downcase rescue nil
|
427
|
+
item_instance = catalog_item['instance']
|
428
|
+
item_app = catalog_item['app']
|
429
|
+
item_execution = catalog_item['execution']
|
430
|
+
render_response(json_response, options, catalog_item_object_key) do
|
431
|
+
print_h1 "Catalog Item Details", [], options
|
432
|
+
print cyan
|
433
|
+
show_columns = catalog_item_column_definitions
|
434
|
+
# show_columns.delete("Status") if catalog_item['status'].to_s.lowercase == 'ORDERED'
|
435
|
+
show_columns.delete("Status") if item_instance || item_app || item_execution
|
436
|
+
print_description_list(show_columns, catalog_item)
|
437
|
+
|
438
|
+
if item_config && !item_config.empty?
|
439
|
+
# print_h2 "Configuration", options
|
440
|
+
# print cyan
|
441
|
+
# print as_description_list(item_config, item_config.keys, options)
|
442
|
+
# print "\n", reset
|
443
|
+
end
|
444
|
+
|
445
|
+
if item_type_code == 'instance'
|
446
|
+
if item_instance
|
447
|
+
print_h2 "Instance", options
|
448
|
+
print cyan
|
449
|
+
item_instance_columns = [
|
450
|
+
{"ID" => lambda {|it| it['id'] } },
|
451
|
+
{"NAME" => lambda {|it| it['name'] } },
|
452
|
+
{"STATUS" => lambda {|it| format_instance_status(it) } },
|
453
|
+
]
|
454
|
+
#print as_description_list(item_instance, item_instance_columns, options)
|
455
|
+
print as_pretty_table([item_instance], item_instance_columns, options)
|
456
|
+
# print "\n", reset
|
457
|
+
else
|
458
|
+
print "\n"
|
459
|
+
print yellow, "No instance found", reset, "\n"
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
if item_type_code == 'app' || item_type_code == 'blueprint' || item_type_code == 'apptemplate'
|
464
|
+
if item_app
|
465
|
+
print_h2 "App", options
|
466
|
+
print cyan
|
467
|
+
item_app_columns = [
|
468
|
+
{"ID" => lambda {|it| it['id'] } },
|
469
|
+
{"NAME" => lambda {|it| it['name'] } },
|
470
|
+
{"STATUS" => lambda {|it| format_app_status(it) } },
|
471
|
+
]
|
472
|
+
#print as_description_list(item_app, item_app_columns, options)
|
473
|
+
print as_pretty_table([item_app], item_app_columns, options)
|
474
|
+
# print "\n", reset
|
475
|
+
else
|
476
|
+
print "\n"
|
477
|
+
print yellow, "No app found", reset, "\n"
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
if item_type_code == 'workflow' || item_type_code == 'operationalworkflow' || item_type_code == 'taskset'
|
482
|
+
if item_execution
|
483
|
+
print_h2 "Workflow Results", options
|
484
|
+
print cyan
|
485
|
+
item_workflow_columns = [
|
486
|
+
{"EXECUTION ID" => lambda {|it| item_execution ? item_execution['id'] : '' } },
|
487
|
+
{"CONTEXT TYPE" => lambda {|it| it['name'] } },
|
488
|
+
{"RESOURCE" => lambda {|it| (it['targets'] ? it['targets'].collect { |target| target['name'] }.join(', ') : '') rescue '' } },
|
489
|
+
{"STATUS" => lambda {|it| item_execution ? format_job_execution_status(item_execution) : 'N/A' } },
|
490
|
+
]
|
491
|
+
#print as_description_list(catalog_item, item_workflow_columns, options)
|
492
|
+
print as_pretty_table([catalog_item], item_workflow_columns, options)
|
493
|
+
# print "\n", reset
|
494
|
+
else
|
495
|
+
print "\n"
|
496
|
+
print yellow, "No execution found", reset, "\n"
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
print reset,"\n"
|
501
|
+
end
|
502
|
+
return 0, nil
|
503
|
+
end
|
504
|
+
|
505
|
+
def get_cart(args)
|
506
|
+
params = {}
|
507
|
+
options = {}
|
508
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
509
|
+
opts.banner = subcommand_usage()
|
510
|
+
opts.on('-a', '--details', "Display all details: item configuration." ) do
|
511
|
+
options[:details] = true
|
512
|
+
end
|
513
|
+
build_standard_get_options(opts, options, [:sigdig])
|
514
|
+
opts.footer = <<-EOT
|
515
|
+
Get details of current cart and the items in it.
|
516
|
+
Exits non-zero if cart is empty.
|
517
|
+
EOT
|
518
|
+
end
|
519
|
+
optparse.parse!(args)
|
520
|
+
verify_args!(args:args, optparse:optparse, count:0)
|
521
|
+
connect(options)
|
522
|
+
|
523
|
+
@service_catalog_interface.setopts(options)
|
524
|
+
if options[:dry_run]
|
525
|
+
print_dry_run @service_catalog_interface.dry.get_cart(params)
|
526
|
+
return 0, nil
|
527
|
+
end
|
528
|
+
# skip extra query, list has same data as show right now
|
529
|
+
json_response = @service_catalog_interface.get_cart(params)
|
530
|
+
cart = json_response['cart']
|
531
|
+
cart_items = cart['items'] || []
|
532
|
+
cart_stats = cart['stats'] || {}
|
533
|
+
render_response(json_response, options, 'cart') do
|
534
|
+
print_h1 "Catalog Cart", [], options
|
535
|
+
print_order_details(cart, options)
|
536
|
+
end
|
537
|
+
if cart_items.empty?
|
538
|
+
return 1, "cart is empty"
|
539
|
+
else
|
540
|
+
return 0, nil
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
def update_cart(args)
|
545
|
+
options = {}
|
546
|
+
params = {}
|
547
|
+
payload = {}
|
548
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
549
|
+
opts.banner = subcommand_usage("--name [name]")
|
550
|
+
opts.on('--name [NAME]', String, "Set an optional name for your catalog order") do |val|
|
551
|
+
options[:options]['name'] = val.to_s
|
552
|
+
end
|
553
|
+
build_standard_update_options(opts, options, [:sigdig])
|
554
|
+
opts.footer = <<-EOT
|
555
|
+
Update your cart settings, such as name.
|
556
|
+
EOT
|
557
|
+
end
|
558
|
+
optparse.parse!(args)
|
559
|
+
verify_args!(args:args, optparse:optparse, count:0)
|
560
|
+
connect(options)
|
561
|
+
# fetch current cart
|
562
|
+
# cart = @service_catalog_interface.get_cart()['cart']
|
563
|
+
payload = {}
|
564
|
+
update_cart_object_key = 'order'
|
565
|
+
if options[:payload]
|
566
|
+
payload = options[:payload]
|
567
|
+
payload.deep_merge!({update_cart_object_key => parse_passed_options(options)})
|
568
|
+
else
|
569
|
+
payload.deep_merge!({update_cart_object_key => parse_passed_options(options)})
|
570
|
+
payload.deep_merge!({update_cart_object_key => params})
|
571
|
+
if payload[update_cart_object_key].empty? # || options[:no_prompt]
|
572
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
573
|
+
end
|
574
|
+
end
|
575
|
+
@service_catalog_interface.setopts(options)
|
576
|
+
if options[:dry_run]
|
577
|
+
print_dry_run @service_catalog_interface.dry.update_cart(payload)
|
578
|
+
return
|
579
|
+
end
|
580
|
+
json_response = @service_catalog_interface.update_cart(payload)
|
581
|
+
#cart = json_response['cart']
|
582
|
+
#cart = @service_catalog_interface.get_cart()['cart']
|
583
|
+
render_response(json_response, options, 'cart') do
|
584
|
+
print_green_success "Updated cart"
|
585
|
+
get_cart([] + (options[:remote] ? ["-r",options[:remote]] : []))
|
586
|
+
end
|
587
|
+
return 0, nil
|
588
|
+
end
|
589
|
+
|
590
|
+
def add(args)
|
591
|
+
options = {}
|
592
|
+
params = {}
|
593
|
+
payload = {}
|
594
|
+
type_id = nil
|
595
|
+
workflow_context = nil
|
596
|
+
workflow_target = nil
|
597
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
598
|
+
opts.banner = subcommand_usage("[type] [options]")
|
599
|
+
opts.on('-t', '--type TYPE', String, "Catalog Item Type Name or ID") do |val|
|
600
|
+
type_id = val.to_s
|
601
|
+
end
|
602
|
+
opts.on('--validate','--validate', "Validate Only. Validates the configuration and skips adding the item.") do
|
603
|
+
options[:validate_only] = true
|
604
|
+
end
|
605
|
+
opts.on('--context [instance|server]', String, "Context Type for operational workflow types") do |val|
|
606
|
+
workflow_context = val.to_s
|
607
|
+
end
|
608
|
+
opts.on('--target ID', String, "Target Resource (Instance or Server) for operational workflow types") do |val|
|
609
|
+
workflow_target = val.to_s
|
610
|
+
end
|
611
|
+
opts.add_hidden_option('--sigdig')
|
612
|
+
build_standard_update_options(opts, options, [:sigdig])
|
613
|
+
opts.footer = <<-EOT
|
614
|
+
Add an item to your cart
|
615
|
+
[type] is required, this is name or id of a catalog item type.
|
616
|
+
Catalog item types may require additional configuration.
|
617
|
+
EOT
|
618
|
+
end
|
619
|
+
optparse.parse!(args)
|
620
|
+
verify_args!(args:args, optparse:optparse, min:0)
|
621
|
+
connect(options)
|
622
|
+
if args.count > 0
|
623
|
+
type_id = args.join(" ")
|
624
|
+
end
|
625
|
+
payload = {}
|
626
|
+
add_item_object_key = 'item'
|
627
|
+
payload = {add_item_object_key => {} }
|
628
|
+
if options[:payload]
|
629
|
+
payload = options[:payload]
|
630
|
+
payload.deep_merge!({add_item_object_key => parse_passed_options(options)})
|
631
|
+
else
|
632
|
+
payload.deep_merge!({add_item_object_key => parse_passed_options(options)})
|
633
|
+
# prompt for Type
|
634
|
+
if type_id
|
635
|
+
catalog_item_type = find_catalog_item_type_by_name_or_id(type_id)
|
636
|
+
return [1, "catalog item type not found"] if catalog_item_type.nil?
|
637
|
+
elsif
|
638
|
+
catalog_type_option_type = {'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
|
639
|
+
# @options_interface.options_for_source("licenseTypes", {})['data']
|
640
|
+
@service_catalog_interface.list_types({max:10000})['catalogItemTypes'].collect {|it|
|
641
|
+
{'name' => it['name'], 'value' => it['id']}
|
642
|
+
} }, 'required' => true, 'description' => 'Catalog Item Type name or id'}
|
643
|
+
type_id = Morpheus::Cli::OptionTypes.prompt([catalog_type_option_type], options[:options], @api_client, options[:params])['type']
|
644
|
+
catalog_item_type = find_catalog_item_type_by_name_or_id(type_id.to_s)
|
645
|
+
return [1, "catalog item type not found"] if catalog_item_type.nil?
|
646
|
+
end
|
647
|
+
# use name instead of id
|
648
|
+
payload[add_item_object_key]['type'] = {'name' => catalog_item_type['name']}
|
649
|
+
#payload[add_item_object_key]['type'] = {'id' => catalog_item_type['id']}
|
650
|
+
|
651
|
+
# this is silly, need to load by id to get optionTypes
|
652
|
+
# maybe do ?name=foo&includeOptionTypes=true
|
653
|
+
if catalog_item_type['optionTypes'].nil?
|
654
|
+
catalog_item_type = find_catalog_item_type_by_id(catalog_item_type['id'])
|
655
|
+
return [1, "catalog item type not found"] if catalog_item_type.nil?
|
656
|
+
end
|
657
|
+
catalog_option_types = catalog_item_type['optionTypes']
|
658
|
+
# instead of config.customOptions just use config...
|
659
|
+
catalog_option_types = catalog_option_types.collect {|it|
|
660
|
+
it['fieldContext'] = 'config'
|
661
|
+
it
|
662
|
+
}
|
663
|
+
if catalog_option_types && !catalog_option_types.empty?
|
664
|
+
config_prompt = Morpheus::Cli::OptionTypes.prompt(catalog_option_types, options[:options], @api_client, {})['config']
|
665
|
+
payload[add_item_object_key].deep_merge!({'config' => config_prompt})
|
666
|
+
end
|
667
|
+
if workflow_context
|
668
|
+
payload[add_item_object_key]['context'] = workflow_context
|
669
|
+
else
|
670
|
+
# the catalog item type determines if context selection is required
|
671
|
+
# only blank string means you can choose? err
|
672
|
+
if catalog_item_type['context'] == ''
|
673
|
+
context_option_type = {'fieldName' => 'context', 'fieldLabel' => 'Context Type', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
|
674
|
+
[{'name' => "none", 'value' => "appliance"}, {'name' => "Instance", 'value' => "instance"}, {'name' => "Server", 'value' => "server"}]
|
675
|
+
}, 'required' => true, 'description' => 'Context for operational workflow, determines target type', 'defaultValue' => 'instance'}
|
676
|
+
workflow_context = Morpheus::Cli::OptionTypes.prompt([context_option_type], options[:options], @api_client, options[:params])['context']
|
677
|
+
elsif !catalog_item_type['context'].nil?
|
678
|
+
workflow_context = catalog_item_type['context']
|
679
|
+
end
|
680
|
+
payload[add_item_object_key]['context'] = workflow_context
|
681
|
+
end
|
682
|
+
|
683
|
+
if workflow_target
|
684
|
+
payload[add_item_object_key]['targets'] = [{id: workflow_target}]
|
685
|
+
else
|
686
|
+
# prompt for Resource (target)
|
687
|
+
if workflow_context == 'instance'
|
688
|
+
target_option_type = {'fieldName' => 'target', 'fieldLabel' => 'Resource (Instance)', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
|
689
|
+
# todo: @instances_interface should not be required here
|
690
|
+
# @options_interface.options_for_source("instances", {})['data']
|
691
|
+
@instances_interface.list({max:10000})['instances'].collect {|it|
|
692
|
+
{'name' => it['name'], 'value' => it['id']}
|
693
|
+
} }, 'required' => true, 'description' => 'Target Instance'}
|
694
|
+
workflow_target = Morpheus::Cli::OptionTypes.prompt([target_option_type], options[:options], @api_client, options[:params])['target']
|
695
|
+
payload[add_item_object_key]['targets'] = [{id: workflow_target}]
|
696
|
+
payload[add_item_object_key].delete('target')
|
697
|
+
elsif workflow_context == 'server'
|
698
|
+
target_option_type = {'fieldName' => 'target', 'fieldLabel' => 'Resource (Server)', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
|
699
|
+
# todo: @servers_interface should not be required here
|
700
|
+
# @options_interface.options_for_source("searchServers", {})['data']
|
701
|
+
@servers_interface.list({max:10000})['servers'].collect {|it|
|
702
|
+
{'name' => it['name'], 'value' => it['id']}
|
703
|
+
} }, 'required' => true, 'description' => 'Target Server'}
|
704
|
+
workflow_target = Morpheus::Cli::OptionTypes.prompt([target_option_type], options[:options], @api_client, options[:params])['target']
|
705
|
+
payload[add_item_object_key]['targets'] = [{id: workflow_target}]
|
706
|
+
payload[add_item_object_key].delete('target')
|
707
|
+
end
|
708
|
+
end
|
709
|
+
end
|
710
|
+
if options[:validate_only]
|
711
|
+
params['validate'] = true
|
712
|
+
end
|
713
|
+
@service_catalog_interface.setopts(options)
|
714
|
+
if options[:dry_run]
|
715
|
+
print_dry_run @service_catalog_interface.dry.create_cart_item(payload, params)
|
716
|
+
return
|
717
|
+
end
|
718
|
+
json_response = @service_catalog_interface.create_cart_item(payload, params)
|
719
|
+
cart_item = json_response['item']
|
720
|
+
render_response(json_response, options) do
|
721
|
+
if options[:validate_only]
|
722
|
+
if json_response['success']
|
723
|
+
print_h2 "Validated Cart Item", [], options
|
724
|
+
cart_item_columns = {
|
725
|
+
"Type" => lambda {|it| it['type']['name'] rescue '' },
|
726
|
+
#"Qty" => lambda {|it| it['quantity'] },
|
727
|
+
"Price" => lambda {|it| it['price'] ? format_money(it['price'] , it['currency'], {sigdig:options[:sigdig] || default_sigdig}) : "No pricing configured" },
|
728
|
+
"Status" => lambda {|it|
|
729
|
+
status_string = format_catalog_item_status(it)
|
730
|
+
if it['errorMessage'].to_s != ""
|
731
|
+
status_string << " - #{it['errorMessage']}"
|
732
|
+
end
|
733
|
+
status_string
|
734
|
+
},
|
735
|
+
#"Config" => lambda {|it| truncate_string(format_name_values(it['config']), 50) }
|
736
|
+
}
|
737
|
+
print as_pretty_table([cart_item], cart_item_columns.upcase_keys!)
|
738
|
+
print reset, "\n"
|
739
|
+
print_green_success(json_response['msg'] || "Item is valid")
|
740
|
+
print reset, "\n"
|
741
|
+
else
|
742
|
+
# not needed because it will be http 400
|
743
|
+
print_rest_errors(json_response, options)
|
744
|
+
end
|
745
|
+
else
|
746
|
+
print_green_success "Added item to cart"
|
747
|
+
get_cart([] + (options[:remote] ? ["-r",options[:remote]] : []))
|
748
|
+
end
|
749
|
+
end
|
750
|
+
if json_response['success']
|
751
|
+
return 0, nil
|
752
|
+
else
|
753
|
+
# not needed because it will be http 400
|
754
|
+
return 1, json_response['msg'] || 'request failed'
|
755
|
+
end
|
756
|
+
end
|
757
|
+
|
758
|
+
def update_cart_item(args)
|
759
|
+
#todo
|
760
|
+
raise_command_error "Not yet implemented"
|
761
|
+
end
|
762
|
+
|
763
|
+
def remove_cart_item(args)
|
764
|
+
options = {}
|
765
|
+
params = {}
|
766
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
767
|
+
opts.banner = subcommand_usage("[id]")
|
768
|
+
build_standard_remove_options(opts, options, [:sigdig])
|
769
|
+
opts.footer = <<-EOT
|
770
|
+
Delete an item from the cart.
|
771
|
+
[id] is required. This is the id of a cart item (also matches on type)
|
772
|
+
EOT
|
773
|
+
end
|
774
|
+
optparse.parse!(args)
|
775
|
+
verify_args!(args:args, optparse:optparse, max:1)
|
776
|
+
connect(options)
|
777
|
+
|
778
|
+
# fetch current cart
|
779
|
+
cart = @service_catalog_interface.get_cart()['cart']
|
780
|
+
cart_items = cart['items'] || []
|
781
|
+
cart_item = nil
|
782
|
+
item_id = args[0]
|
783
|
+
# match cart item on id OR type.name
|
784
|
+
if item_id.nil?
|
785
|
+
cart_item_option_type = {'fieldName' => 'id', 'fieldLabel' => 'Cart Item', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
|
786
|
+
# cart_items.collect {|ci| {'name' => ci['name'], 'value' => ci['id']} }
|
787
|
+
cart_items.collect {|ci| {'name' => (ci['type']['name'] rescue ci['name']), 'value' => ci['id']} }
|
788
|
+
}, 'required' => true, 'description' => 'Cart Item to be removed'}
|
789
|
+
item_id = Morpheus::Cli::OptionTypes.prompt([cart_item_option_type], options[:options], @api_client)['id']
|
790
|
+
end
|
791
|
+
if item_id
|
792
|
+
cart_item = cart_items.find { |ci| ci['id'] == item_id.to_i }
|
793
|
+
if cart_item.nil?
|
794
|
+
matching_items = cart_items.select { |ci| (ci['type']['name'] rescue nil) == item_id.to_s }
|
795
|
+
if matching_items.size > 1
|
796
|
+
print_red_alert "#{matching_items.size} cart items matched '#{item_id}'"
|
797
|
+
cart_item_columns = [
|
798
|
+
{"ID" => lambda {|it| it['id'] } },
|
799
|
+
#{"NAME" => lambda {|it| it['name'] } },
|
800
|
+
{"Type" => lambda {|it| it['type']['name'] rescue '' } },
|
801
|
+
#{"Qty" => lambda {|it| it['quantity'] } },
|
802
|
+
{"Price" => lambda {|it| it['price'] ? format_money(it['price'] , it['currency'], {sigdig:options[:sigdig] || default_sigdig}) : "No pricing configured" } },
|
803
|
+
]
|
804
|
+
puts_error as_pretty_table(matching_items, cart_item_columns, {color:red})
|
805
|
+
print_red_alert "Try using ID instead"
|
806
|
+
print reset,"\n"
|
807
|
+
return nil
|
808
|
+
end
|
809
|
+
cart_item = matching_items[0]
|
810
|
+
end
|
811
|
+
end
|
812
|
+
if cart_item.nil?
|
813
|
+
err = "Cart item not found for '#{item_id}'"
|
814
|
+
print_red_alert err
|
815
|
+
return 1, err
|
816
|
+
end
|
817
|
+
|
818
|
+
@service_catalog_interface.setopts(options)
|
819
|
+
if options[:dry_run]
|
820
|
+
print_dry_run @service_catalog_interface.dry.destroy_cart_item(cart_item['id'], params)
|
821
|
+
return
|
822
|
+
end
|
823
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to remove item '#{cart_item['type']['name'] rescue cart_item['id']}' from your cart?")
|
824
|
+
return 9, "aborted command"
|
825
|
+
end
|
826
|
+
json_response = @service_catalog_interface.destroy_cart_item(cart_item['id'], params)
|
827
|
+
render_response(json_response, options) do
|
828
|
+
print_green_success "Removed item from cart"
|
829
|
+
get_cart([] + (options[:remote] ? ["-r",options[:remote]] : []))
|
830
|
+
end
|
831
|
+
return 0, nil
|
832
|
+
end
|
833
|
+
|
834
|
+
def clear_cart(args)
|
835
|
+
options = {}
|
836
|
+
params = {}
|
837
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
838
|
+
opts.banner = subcommand_usage("--name [name]")
|
839
|
+
build_standard_remove_options(opts, options, [:sigdig])
|
840
|
+
opts.footer = <<-EOT
|
841
|
+
Clear your cart.
|
842
|
+
This will empty the cart, deleting all items.
|
843
|
+
EOT
|
844
|
+
end
|
845
|
+
optparse.parse!(args)
|
846
|
+
verify_args!(args:args, optparse:optparse, count:0)
|
847
|
+
connect(options)
|
848
|
+
# fetch current cart
|
849
|
+
# cart = @service_catalog_interfaceg.get_cart()['cart']
|
850
|
+
params.merge!(parse_query_options(options))
|
851
|
+
@service_catalog_interface.setopts(options)
|
852
|
+
if options[:dry_run]
|
853
|
+
print_dry_run @service_catalog_interface.dry.clear_cart(params)
|
854
|
+
return
|
855
|
+
end
|
856
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to clear your cart?")
|
857
|
+
return 9, "aborted command"
|
858
|
+
end
|
859
|
+
json_response = @service_catalog_interface.clear_cart(params)
|
860
|
+
render_response(json_response, options, 'cart') do
|
861
|
+
print_green_success "Cleared cart"
|
862
|
+
get_cart([] + (options[:remote] ? ["-r",options[:remote]] : []))
|
863
|
+
end
|
864
|
+
return 0, nil
|
865
|
+
end
|
866
|
+
|
867
|
+
def checkout(args)
|
868
|
+
options = {}
|
869
|
+
params = {}
|
870
|
+
payload = {}
|
871
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
872
|
+
opts.banner = subcommand_usage()
|
873
|
+
build_standard_add_options(opts, options, [:auto_confirm, :sigdig])
|
874
|
+
opts.footer = <<-EOT
|
875
|
+
Checkout to complete your cart and place an order.
|
876
|
+
EOT
|
877
|
+
end
|
878
|
+
optparse.parse!(args)
|
879
|
+
verify_args!(args:args, optparse:optparse, count:0)
|
880
|
+
connect(options)
|
881
|
+
# fetch current cart
|
882
|
+
# cart = @service_catalog_interface.get_cart()['cart']
|
883
|
+
params.merge!(parse_query_options(options))
|
884
|
+
payload = {}
|
885
|
+
if options[:payload]
|
886
|
+
payload = options[:payload]
|
887
|
+
end
|
888
|
+
update_cart_object_key = 'order'
|
889
|
+
passed_options = parse_passed_options(options)
|
890
|
+
payload.deep_merge!({update_cart_object_key => passed_options}) unless passed_options.empty?
|
891
|
+
|
892
|
+
@service_catalog_interface.setopts(options)
|
893
|
+
if options[:dry_run]
|
894
|
+
print_dry_run @service_catalog_interface.dry.checkout(payload)
|
895
|
+
return
|
896
|
+
end
|
897
|
+
|
898
|
+
# checkout
|
899
|
+
print_h1 "Checkout"
|
900
|
+
|
901
|
+
# review cart
|
902
|
+
# should load this first, but do this to avoid double load
|
903
|
+
cmd_result, cmd_err = get_cart(["--thin"] + (options[:remote] ? ["-r",options[:remote]] : []))
|
904
|
+
if cmd_result != 0
|
905
|
+
print_red_alert "You must add items before you can checkout. Try `catalog add`"
|
906
|
+
return cmd_result, cmd_err
|
907
|
+
end
|
908
|
+
|
909
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to checkout and place an order?")
|
910
|
+
return 9, "aborted command"
|
911
|
+
end
|
912
|
+
json_response = @service_catalog_interface.checkout(payload, params)
|
913
|
+
render_response(json_response, options) do
|
914
|
+
print_green_success "Order placed"
|
915
|
+
# ok so this is delayed because list does not return all statuses right now..
|
916
|
+
#list([] + (options[:remote] ? ["-r",options[:remote]] : []))
|
917
|
+
end
|
918
|
+
return 0, nil
|
919
|
+
end
|
920
|
+
|
921
|
+
def add_order(args)
|
922
|
+
options = {}
|
923
|
+
params = {}
|
924
|
+
payload = {}
|
925
|
+
type_id = nil
|
926
|
+
workflow_context = nil
|
927
|
+
workflow_target = nil
|
928
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
929
|
+
opts.banner = subcommand_usage("[type] [options]")
|
930
|
+
opts.on('-t', '--type TYPE', String, "Catalog Item Type Name or ID") do |val|
|
931
|
+
type_id = val.to_s
|
932
|
+
end
|
933
|
+
opts.on('--validate','--validate', "Validate Only. Validates the configuration and skips creating the order.") do
|
934
|
+
options[:validate_only] = true
|
935
|
+
end
|
936
|
+
opts.on('-a', '--details', "Display all details: item configuration." ) do
|
937
|
+
options[:details] = true
|
938
|
+
end
|
939
|
+
opts.on('--context [instance|server]', String, "Context Type for operational workflow types") do |val|
|
940
|
+
workflow_context = val.to_s
|
941
|
+
end
|
942
|
+
opts.on('--target ID', String, "Target Resource (Instance or Server) for operational workflow types") do |val|
|
943
|
+
workflow_target = val.to_s
|
944
|
+
end
|
945
|
+
build_standard_add_options(opts, options, [:sigdig])
|
946
|
+
opts.footer = <<-EOT
|
947
|
+
Place an order for new inventory.
|
948
|
+
This allows creating a new order without using the cart.
|
949
|
+
The order must contain one or more items, each with a valid type and configuration.
|
950
|
+
By default the order is placed right away.
|
951
|
+
Use the --validate option to validate and review the order without actually placing it.
|
952
|
+
EOT
|
953
|
+
end
|
954
|
+
optparse.parse!(args)
|
955
|
+
verify_args!(args:args, optparse:optparse, min:0)
|
956
|
+
connect(options)
|
957
|
+
if args.count > 0
|
958
|
+
type_id = args.join(" ")
|
959
|
+
end
|
960
|
+
payload = {}
|
961
|
+
order_object_key = 'order'
|
962
|
+
payload = {order_object_key => {} }
|
963
|
+
passed_options = parse_passed_options(options)
|
964
|
+
if options[:payload]
|
965
|
+
payload = options[:payload]
|
966
|
+
payload.deep_merge!({order_object_key => passed_options}) unless passed_options.empty?
|
967
|
+
else
|
968
|
+
payload.deep_merge!({order_object_key => passed_options}) unless passed_options.empty?
|
969
|
+
|
970
|
+
# Prompt for 1-N Types
|
971
|
+
# still_prompting = options[:no_prompt] != true
|
972
|
+
still_prompting = true
|
973
|
+
available_catalog_item_types = @service_catalog_interface.list_types({max:10000})['catalogItemTypes'].collect {|it|
|
974
|
+
{'name' => it['name'], 'value' => it['id']}
|
975
|
+
}
|
976
|
+
type_cache = {} # prevent repeat lookups
|
977
|
+
while still_prompting do
|
978
|
+
item_payload = {}
|
979
|
+
# prompt for Type
|
980
|
+
if type_id
|
981
|
+
catalog_item_type = type_cache[type_id.to_s] || find_catalog_item_type_by_name_or_id(type_id)
|
982
|
+
return [1, "catalog item type not found"] if catalog_item_type.nil?
|
983
|
+
elsif
|
984
|
+
type_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => available_catalog_item_types, 'required' => true, 'description' => 'Catalog Item Type name or id'}], options[:options], @api_client, options[:params])['type']
|
985
|
+
catalog_item_type = type_cache[type_id.to_s] || find_catalog_item_type_by_name_or_id(type_id.to_s)
|
986
|
+
return [1, "catalog item type not found"] if catalog_item_type.nil?
|
987
|
+
end
|
988
|
+
type_cache[type_id.to_s] = catalog_item_type
|
989
|
+
# use name instead of id
|
990
|
+
item_payload['type'] = {'name' => catalog_item_type['name']}
|
991
|
+
#payload[add_item_object_key]['type'] = {'id' => catalog_item_type['id']}
|
992
|
+
|
993
|
+
# this is silly, need to load by id to get optionTypes
|
994
|
+
# maybe do ?name=foo&includeOptionTypes=true
|
995
|
+
if catalog_item_type['optionTypes'].nil?
|
996
|
+
catalog_item_type = find_catalog_item_type_by_id(catalog_item_type['id'])
|
997
|
+
return [1, "catalog item type not found"] if catalog_item_type.nil?
|
998
|
+
end
|
999
|
+
catalog_option_types = catalog_item_type['optionTypes']
|
1000
|
+
# instead of config.customOptions just use config...
|
1001
|
+
catalog_option_types = catalog_option_types.collect {|it|
|
1002
|
+
it['fieldContext'] = 'config'
|
1003
|
+
it
|
1004
|
+
}
|
1005
|
+
if catalog_option_types && !catalog_option_types.empty?
|
1006
|
+
config_prompt = Morpheus::Cli::OptionTypes.prompt(catalog_option_types, options[:options], @api_client, {})['config']
|
1007
|
+
item_payload.deep_merge!({'config' => config_prompt})
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
if workflow_context
|
1011
|
+
item_payload['context'] = workflow_context
|
1012
|
+
else
|
1013
|
+
# the catalog item type determines if context selection is required
|
1014
|
+
# only blank string means you can choose? err
|
1015
|
+
if catalog_item_type['context'] == ''
|
1016
|
+
context_option_type = {'fieldName' => 'context', 'fieldLabel' => 'Context Type', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
|
1017
|
+
[{'name' => "Instance", 'value' => "instance"}, {'name' => "Server", 'value' => "server"}]
|
1018
|
+
}, 'required' => true, 'description' => 'Context for operational workflow, determines target type', 'defaultValue' => 'instance'}
|
1019
|
+
workflow_context = Morpheus::Cli::OptionTypes.prompt([context_option_type], options[:options], @api_client, options[:params])['context']
|
1020
|
+
elsif !catalog_item_type['context'].nil?
|
1021
|
+
workflow_context = catalog_item_type['context']
|
1022
|
+
end
|
1023
|
+
item_payload['context'] = workflow_context if workflow_context
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
if workflow_target
|
1027
|
+
item_payload['targets'] = [{id: workflow_target}]
|
1028
|
+
else
|
1029
|
+
# prompt for Resource (target)
|
1030
|
+
if workflow_context == 'instance'
|
1031
|
+
target_option_type = {'fieldName' => 'target', 'fieldLabel' => 'Resource (Instance)', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
|
1032
|
+
# todo: @instances_interface should not be required here
|
1033
|
+
# @options_interface.options_for_source("instances", {})['data']
|
1034
|
+
@instances_interface.list({max:10000})['instances'].collect {|it|
|
1035
|
+
{'name' => it['name'], 'value' => it['id']}
|
1036
|
+
} }, 'required' => true, 'description' => 'Target Instance'}
|
1037
|
+
workflow_target = Morpheus::Cli::OptionTypes.prompt([target_option_type], options[:options], @api_client, options[:params])['target']
|
1038
|
+
item_payload['targets'] = [{id: workflow_target}]
|
1039
|
+
item_payload.delete('target')
|
1040
|
+
elsif workflow_context == 'server'
|
1041
|
+
target_option_type = {'fieldName' => 'target', 'fieldLabel' => 'Resource (Server)', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
|
1042
|
+
# todo: @servers_interface should not be required here
|
1043
|
+
# @options_interface.options_for_source("searchServers", {})['data']
|
1044
|
+
@servers_interface.list({max:10000})['servers'].collect {|it|
|
1045
|
+
{'name' => it['name'], 'value' => it['id']}
|
1046
|
+
} }, 'required' => true, 'description' => 'Target Server'}
|
1047
|
+
workflow_target = Morpheus::Cli::OptionTypes.prompt([target_option_type], options[:options], @api_client, options[:params])['target']
|
1048
|
+
item_payload['targets'] = [{id: workflow_target}]
|
1049
|
+
item_payload.delete('target')
|
1050
|
+
end
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
payload[order_object_key]['items'] ||= []
|
1054
|
+
payload[order_object_key]['items'] << item_payload
|
1055
|
+
|
1056
|
+
if options[:no_prompt]
|
1057
|
+
still_prompting = false
|
1058
|
+
else
|
1059
|
+
if Morpheus::Cli::OptionTypes.confirm("Add another item?", {default: false})
|
1060
|
+
still_prompting = true
|
1061
|
+
# clear values for subsequent items, should just use for a different fieldContext instead..
|
1062
|
+
type_id = nil
|
1063
|
+
options[:options] = {}
|
1064
|
+
else
|
1065
|
+
still_prompting = false
|
1066
|
+
end
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
end
|
1070
|
+
|
1071
|
+
|
1072
|
+
end
|
1073
|
+
if options[:validate_only]
|
1074
|
+
params['validate'] = true
|
1075
|
+
#payload['validate'] = true
|
1076
|
+
end
|
1077
|
+
@service_catalog_interface.setopts(options)
|
1078
|
+
if options[:dry_run]
|
1079
|
+
print_dry_run @service_catalog_interface.dry.create_order(payload, params)
|
1080
|
+
return
|
1081
|
+
end
|
1082
|
+
json_response = @service_catalog_interface.create_order(payload, params)
|
1083
|
+
order = json_response['order'] || json_response['cart']
|
1084
|
+
render_response(json_response, options) do
|
1085
|
+
if options[:validate_only]
|
1086
|
+
if json_response['success']
|
1087
|
+
print_h2 "Review Order", [], options
|
1088
|
+
print_order_details(order, options)
|
1089
|
+
print_green_success(json_response['msg'] || "Order is valid")
|
1090
|
+
print reset, "\n"
|
1091
|
+
else
|
1092
|
+
# not needed because it will be http 400
|
1093
|
+
print_rest_errors(json_response, options)
|
1094
|
+
end
|
1095
|
+
else
|
1096
|
+
print_green_success "Order placed"
|
1097
|
+
print_h2 "Order Details", [], options
|
1098
|
+
print_order_details(order, options)
|
1099
|
+
end
|
1100
|
+
end
|
1101
|
+
if json_response['success']
|
1102
|
+
return 0, nil
|
1103
|
+
else
|
1104
|
+
# not needed because it will be http 400
|
1105
|
+
return 1, json_response['msg'] || 'request failed'
|
1106
|
+
end
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
def remove(args)
|
1110
|
+
options = {}
|
1111
|
+
params = {}
|
1112
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1113
|
+
opts.banner = subcommand_usage("[item] [options]")
|
1114
|
+
opts.on('--remove-instances [true|false]', String, "Remove instances. Default is true. Applies to apps only.") do |val|
|
1115
|
+
params[:removeInstances] = ['true','on','1',''].include?(val.to_s.downcase)
|
1116
|
+
end
|
1117
|
+
opts.on( '-B', '--keep-backups [true|false]', "Preserve copy of backups. Default is false." ) do
|
1118
|
+
params[:keepBackups] = ['true','on','1',''].include?(val.to_s.downcase)
|
1119
|
+
end
|
1120
|
+
opts.on('--preserve-volumes [on|off]', String, "Preserve Volumes. Default is off. Applies to certain types only.") do |val|
|
1121
|
+
params[:preserveVolumes] = ['true','on','1',''].include?(val.to_s.downcase)
|
1122
|
+
end
|
1123
|
+
opts.on('--releaseEIPs [true|false]', String, "Release EIPs. Default is on. Applies to Amazon only.") do |val|
|
1124
|
+
params[:releaseEIPs] = ['true','on','1',''].include?(val.to_s.downcase)
|
1125
|
+
end
|
1126
|
+
opts.on( '-f', '--force', "Force Delete" ) do
|
1127
|
+
params[:force] = true
|
1128
|
+
end
|
1129
|
+
build_standard_remove_options(opts, options, [:sigdig])
|
1130
|
+
opts.footer = <<-EOT
|
1131
|
+
Delete a catalog inventory item.
|
1132
|
+
This removes the item from the inventory and deprovisions the associated instance(s).
|
1133
|
+
[item] is required. This is the name or id of a catalog inventory item.
|
1134
|
+
EOT
|
1135
|
+
end
|
1136
|
+
optparse.parse!(args)
|
1137
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
1138
|
+
connect(options)
|
1139
|
+
|
1140
|
+
catalog_item = find_catalog_item_by_name_or_id(args[0])
|
1141
|
+
return 1 if catalog_item.nil?
|
1142
|
+
|
1143
|
+
is_app = (catalog_item['type']['type'] == 'app' || catalog_item['type']['type'] == 'blueprint' || catalog_item['type']['type'] == 'apptemplate') rescue false
|
1144
|
+
|
1145
|
+
params.merge!(parse_query_options(options))
|
1146
|
+
# delete dialog
|
1147
|
+
# we do not have provisioning settings right now to know if we can prompt for volumes / eips
|
1148
|
+
# skip force because it is excessive prompting...
|
1149
|
+
delete_prompt_options = [
|
1150
|
+
{'fieldName' => 'removeInstances', 'fieldLabel' => 'Remove Instances', 'type' => 'checkbox', 'defaultValue' => true},
|
1151
|
+
{'fieldName' => 'keepBackups', 'fieldLabel' => 'Preserve Backups', 'type' => 'checkbox', 'defaultValue' => false},
|
1152
|
+
#{'fieldName' => 'preserveVolumes', 'fieldLabel' => 'Preserve Volumes', 'type' => 'checkbox', 'defaultValue' => false},
|
1153
|
+
# {'fieldName' => 'releaseEIPs', 'fieldLabel' => 'Release EIPs. Default is on. Applies to Amazon only.', 'type' => 'checkbox', 'defaultValue' => true},
|
1154
|
+
#{'fieldName' => 'force', 'fieldLabel' => 'Force Delete', 'type' => 'checkbox', 'defaultValue' => false},
|
1155
|
+
]
|
1156
|
+
if !is_app
|
1157
|
+
delete_prompt_options.reject! {|it| it['fieldName'] == 'removeInstances'}
|
1158
|
+
end
|
1159
|
+
options[:options][:no_prompt] = true if options[:yes] # -y could always mean do not prompt too..
|
1160
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt(delete_prompt_options, options[:options], @api_client)
|
1161
|
+
v_prompt.booleanize! # 'on' => true
|
1162
|
+
params.deep_merge!(v_prompt)
|
1163
|
+
|
1164
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the inventory item #{catalog_item['id']} '#{catalog_item['name']}'?")
|
1165
|
+
return 9, "aborted command"
|
1166
|
+
end
|
1167
|
+
@service_catalog_interface.setopts(options)
|
1168
|
+
if options[:dry_run]
|
1169
|
+
print_dry_run @service_catalog_interface.dry.destroy_inventory(catalog_item['id'], params)
|
1170
|
+
return
|
1171
|
+
end
|
1172
|
+
json_response = @service_catalog_interface.destroy_inventory(catalog_item['id'], params)
|
1173
|
+
render_response(json_response, options) do
|
1174
|
+
print_green_success "Removing catalog item"
|
1175
|
+
end
|
1176
|
+
return 0, nil
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
private
|
1180
|
+
|
1181
|
+
# Catalog Item Types helpers
|
1182
|
+
|
1183
|
+
def catalog_item_type_column_definitions()
|
1184
|
+
{
|
1185
|
+
"ID" => 'id',
|
1186
|
+
"Name" => 'name',
|
1187
|
+
"Description" => 'description',
|
1188
|
+
# "Type" => lambda {|it| format_catalog_type(it) },
|
1189
|
+
# "Blueprint" => lambda {|it| it['blueprint'] ? it['blueprint']['name'] : nil },
|
1190
|
+
# "Enabled" => lambda {|it| format_boolean(it['enabled']) },
|
1191
|
+
"Featured" => lambda {|it| format_boolean(it['featured']) },
|
1192
|
+
#"Config" => lambda {|it| it['config'] },
|
1193
|
+
# "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
1194
|
+
# "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
|
1195
|
+
}
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
def catalog_item_type_object_key
|
1199
|
+
'catalogItemType'
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
def catalog_item_type_list_key
|
1203
|
+
'catalogItemTypes'
|
1204
|
+
end
|
1205
|
+
|
1206
|
+
def find_catalog_item_type_by_name_or_id(val)
|
1207
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
1208
|
+
return find_catalog_item_type_by_id(val)
|
1209
|
+
else
|
1210
|
+
return find_catalog_item_type_by_name(val)
|
1211
|
+
end
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
# this returns optionTypes and list does not..
|
1215
|
+
def find_catalog_item_type_by_id(id)
|
1216
|
+
begin
|
1217
|
+
json_response = @service_catalog_interface.get_type(id.to_i)
|
1218
|
+
return json_response[catalog_item_type_object_key]
|
1219
|
+
rescue RestClient::Exception => e
|
1220
|
+
if e.response && e.response.code == 404
|
1221
|
+
print_red_alert "Catalog item type not found by id '#{id}'"
|
1222
|
+
else
|
1223
|
+
raise e
|
1224
|
+
end
|
1225
|
+
end
|
1226
|
+
end
|
1227
|
+
|
1228
|
+
def find_catalog_item_type_by_name(name)
|
1229
|
+
json_response = @service_catalog_interface.list_types({name: name.to_s})
|
1230
|
+
catalog_item_types = json_response[catalog_item_type_list_key]
|
1231
|
+
if catalog_item_types.empty?
|
1232
|
+
print_red_alert "Catalog item type not found by name '#{name}'"
|
1233
|
+
return nil
|
1234
|
+
elsif catalog_item_types.size > 1
|
1235
|
+
print_red_alert "#{catalog_item_types.size} catalog item types found by name '#{name}'"
|
1236
|
+
puts_error as_pretty_table(catalog_item_types, [:id, :name], {color:red})
|
1237
|
+
print_red_alert "Try using ID instead"
|
1238
|
+
print reset,"\n"
|
1239
|
+
return nil
|
1240
|
+
else
|
1241
|
+
return catalog_item_types[0]
|
1242
|
+
end
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
def catalog_item_column_definitions()
|
1246
|
+
{
|
1247
|
+
"ID" => 'id',
|
1248
|
+
"Name" => 'name',
|
1249
|
+
#"Description" => 'description',
|
1250
|
+
"Type" => lambda {|it| it['type']['name'] rescue '' },
|
1251
|
+
#"Qty" => lambda {|it| it['quantity'] },
|
1252
|
+
# "Enabled" => lambda {|it| format_boolean(it['enabled']) },
|
1253
|
+
|
1254
|
+
# "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
1255
|
+
# "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
|
1256
|
+
"Order Date" => lambda {|it| format_local_dt(it['orderDate']) },
|
1257
|
+
"Status" => lambda {|it| format_catalog_item_status(it) },
|
1258
|
+
# "Config" => lambda {|it| format_name_values(it['config']) },
|
1259
|
+
}
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
# Catalog Items (Inventory) helpers
|
1263
|
+
|
1264
|
+
def catalog_item_object_key
|
1265
|
+
'item'
|
1266
|
+
end
|
1267
|
+
|
1268
|
+
def catalog_item_list_key
|
1269
|
+
'items'
|
1270
|
+
end
|
1271
|
+
|
1272
|
+
def find_catalog_item_by_name_or_id(val)
|
1273
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
1274
|
+
return find_catalog_item_by_id(val)
|
1275
|
+
else
|
1276
|
+
return find_catalog_item_by_name(val)
|
1277
|
+
end
|
1278
|
+
end
|
1279
|
+
|
1280
|
+
def find_catalog_item_by_id(id)
|
1281
|
+
begin
|
1282
|
+
json_response = @service_catalog_interface.get_inventory(id.to_i)
|
1283
|
+
return json_response[catalog_item_object_key]
|
1284
|
+
rescue RestClient::Exception => e
|
1285
|
+
if e.response && e.response.code == 404
|
1286
|
+
print_red_alert "Catalog item not found by id '#{id}'"
|
1287
|
+
else
|
1288
|
+
raise e
|
1289
|
+
end
|
1290
|
+
end
|
1291
|
+
end
|
1292
|
+
|
1293
|
+
# find by name not yet supported, items do not have a name
|
1294
|
+
def find_catalog_item_by_name(name)
|
1295
|
+
json_response = @service_catalog_interface.list_inventory({name: name.to_s})
|
1296
|
+
catalog_items = json_response[catalog_item_list_key]
|
1297
|
+
if catalog_items.empty?
|
1298
|
+
print_red_alert "Catalog item not found by name '#{name}'"
|
1299
|
+
return nil
|
1300
|
+
elsif catalog_items.size > 1
|
1301
|
+
print_red_alert "#{catalog_items.size} catalog items found by name '#{name}'"
|
1302
|
+
puts_error as_pretty_table(catalog_items, [:id, :name], {color:red})
|
1303
|
+
print_red_alert "Try using ID instead"
|
1304
|
+
print reset,"\n"
|
1305
|
+
return nil
|
1306
|
+
else
|
1307
|
+
return catalog_items[0]
|
1308
|
+
end
|
1309
|
+
end
|
1310
|
+
|
1311
|
+
def format_catalog_item_status(item, return_color=cyan)
|
1312
|
+
out = ""
|
1313
|
+
status_string = item['status'].to_s.upcase
|
1314
|
+
if status_string == 'IN_CART' || status_string == 'IN CART'
|
1315
|
+
out << "#{cyan}IN CART#{return_color}"
|
1316
|
+
elsif status_string == 'ORDERED'
|
1317
|
+
#out << "#{cyan}#{status_string.upcase}#{return_color}"
|
1318
|
+
# show the instance/app/execution status instead of the item status ORDERED
|
1319
|
+
if item['instance']
|
1320
|
+
out << format_instance_status(item['instance'], return_color)
|
1321
|
+
elsif item['app']
|
1322
|
+
out << format_app_status(item['app'], return_color)
|
1323
|
+
elsif item['execution']
|
1324
|
+
out << format_job_execution_status(item['execution'], return_color)
|
1325
|
+
else
|
1326
|
+
out << "#{cyan}#{status_string.upcase}#{return_color}"
|
1327
|
+
end
|
1328
|
+
elsif status_string == 'VALID'
|
1329
|
+
out << "#{green}#{status_string.upcase}#{return_color}"
|
1330
|
+
elsif status_string == 'INVALID'
|
1331
|
+
out << "#{red}#{status_string.upcase}#{return_color}"
|
1332
|
+
elsif status_string == 'FAILED'
|
1333
|
+
out << "#{red}#{status_string.upcase}#{return_color}"
|
1334
|
+
elsif status_string == 'DELETED'
|
1335
|
+
out << "#{red}#{status_string.upcase}#{return_color}" # cyan maybe?
|
1336
|
+
else
|
1337
|
+
out << "#{yellow}#{status_string.upcase}#{return_color}"
|
1338
|
+
end
|
1339
|
+
out
|
1340
|
+
end
|
1341
|
+
|
1342
|
+
def format_order_status(cart, return_color=cyan)
|
1343
|
+
out = ""
|
1344
|
+
cart_items = cart['items']
|
1345
|
+
if cart_items.nil? || cart_items.empty?
|
1346
|
+
out << "#{yellow}EMPTY#{return_color}"
|
1347
|
+
else
|
1348
|
+
# status of first item in cart will work i guess...
|
1349
|
+
item = cart_items.first
|
1350
|
+
status_string = item['status'].to_s.upcase
|
1351
|
+
if status_string == "IN_CART"
|
1352
|
+
# out << "#{cyan}CART (#{cart_items.size()})#{return_color}"
|
1353
|
+
out << "#{cyan}CART#{return_color}"
|
1354
|
+
else
|
1355
|
+
out << format_catalog_item_status(item, return_color)
|
1356
|
+
end
|
1357
|
+
end
|
1358
|
+
out
|
1359
|
+
end
|
1360
|
+
|
1361
|
+
def print_order_details(cart, options)
|
1362
|
+
cart_items = cart['items'] || []
|
1363
|
+
cart_stats = cart['stats'] || {}
|
1364
|
+
if cart_items && cart_items.size > 0
|
1365
|
+
print cyan
|
1366
|
+
cart_show_columns = {
|
1367
|
+
#"Order ID" => 'id',
|
1368
|
+
"Order Name" => 'name',
|
1369
|
+
"Order Items" => lambda {|it| cart['items'].size },
|
1370
|
+
"Order Qty" => lambda {|it| cart['items'].sum {|cart_item| cart_item['quantity'] } },
|
1371
|
+
"Order Status" => lambda {|it| format_order_status(it) },
|
1372
|
+
#"Order Total" => lambda {|it| format_money(cart_stats['price'], cart_stats['currency'], {sigdig:options[:sigdig] || default_sigdig}) + " / #{cart_stats['unit'].to_s.empty? ? 'month' : cart_stats['unit']}" },
|
1373
|
+
#"Items" => lambda {|it| cart['items'].size },
|
1374
|
+
# "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
1375
|
+
# "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
|
1376
|
+
}
|
1377
|
+
if options[:details] != true
|
1378
|
+
cart_show_columns.delete("Order Items")
|
1379
|
+
cart_show_columns.delete("Order Qty")
|
1380
|
+
cart_show_columns.delete("Order Status")
|
1381
|
+
end
|
1382
|
+
cart_show_columns.delete("Order Name") if cart['name'].to_s.empty?
|
1383
|
+
# if !cart_show_columns.empty?
|
1384
|
+
# print_description_list(cart_show_columns, cart)
|
1385
|
+
# print reset, "\n"
|
1386
|
+
# end
|
1387
|
+
|
1388
|
+
if options[:details]
|
1389
|
+
if !cart_show_columns.empty?
|
1390
|
+
print_description_list(cart_show_columns, cart)
|
1391
|
+
# print reset, "\n"
|
1392
|
+
end
|
1393
|
+
#print_h2 "Cart Items"
|
1394
|
+
cart_items.each_with_index do |cart_item, index|
|
1395
|
+
item_config = cart_item['config']
|
1396
|
+
cart_item_columns = [
|
1397
|
+
{"ID" => lambda {|it| it['id'] } },
|
1398
|
+
#{"NAME" => lambda {|it| it['name'] } },
|
1399
|
+
{"Type" => lambda {|it| it['type']['name'] rescue '' } },
|
1400
|
+
#{"Qty" => lambda {|it| it['quantity'] } },
|
1401
|
+
{"Price" => lambda {|it| it['price'] ? format_money(it['price'] , it['currency'], {sigdig:options[:sigdig] || default_sigdig}) : "No pricing configured" } },
|
1402
|
+
{"Status" => lambda {|it|
|
1403
|
+
status_string = format_catalog_item_status(it)
|
1404
|
+
if it['errorMessage'].to_s != ""
|
1405
|
+
status_string << " - #{it['errorMessage']}"
|
1406
|
+
end
|
1407
|
+
status_string
|
1408
|
+
} },
|
1409
|
+
# {"Config" => lambda {|it| format_name_values(it['config']) } },
|
1410
|
+
]
|
1411
|
+
print_h2(index == 0 ? "Item" : "Item #{index+1}", options)
|
1412
|
+
print as_description_list(cart_item, cart_item_columns, options)
|
1413
|
+
# print "\n", reset
|
1414
|
+
if item_config && !item_config.keys.empty?
|
1415
|
+
print_h2("Configuration", options)
|
1416
|
+
print as_description_list(item_config, item_config.keys, options)
|
1417
|
+
print "\n", reset
|
1418
|
+
end
|
1419
|
+
end
|
1420
|
+
else
|
1421
|
+
if !cart_show_columns.empty?
|
1422
|
+
print_description_list(cart_show_columns, cart)
|
1423
|
+
print reset, "\n"
|
1424
|
+
end
|
1425
|
+
#print_h2 "Cart Items"
|
1426
|
+
cart_item_columns = [
|
1427
|
+
{"ID" => lambda {|it| it['id'] } },
|
1428
|
+
#{"NAME" => lambda {|it| it['name'] } },
|
1429
|
+
{"TYPE" => lambda {|it| it['type']['name'] rescue '' } },
|
1430
|
+
#{"QTY" => lambda {|it| it['quantity'] } },
|
1431
|
+
{"PRICE" => lambda {|it| it['price'] ? format_money(it['price'] , it['currency'], {sigdig:options[:sigdig] || default_sigdig}) : "No pricing configured" } },
|
1432
|
+
{"STATUS" => lambda {|it|
|
1433
|
+
status_string = format_catalog_item_status(it)
|
1434
|
+
if it['errorMessage'].to_s != ""
|
1435
|
+
status_string << " - #{it['errorMessage']}"
|
1436
|
+
end
|
1437
|
+
status_string
|
1438
|
+
} },
|
1439
|
+
# {"CONFIG" => lambda {|it|
|
1440
|
+
# truncate_string(format_name_values(it['config']), 50)
|
1441
|
+
# } },
|
1442
|
+
]
|
1443
|
+
print as_pretty_table(cart_items, cart_item_columns)
|
1444
|
+
end
|
1445
|
+
print reset,"\n"
|
1446
|
+
print cyan
|
1447
|
+
if cart_stats['price']
|
1448
|
+
puts "Total: " + format_money(cart_stats['price'], cart_stats['currency'], {sigdig:options[:sigdig] || default_sigdig}) + " / #{cart_stats['unit'].to_s.empty? ? 'month' : cart_stats['unit']}"
|
1449
|
+
else
|
1450
|
+
puts "Total: " + "No pricing configured"
|
1451
|
+
end
|
1452
|
+
print reset,"\n"
|
1453
|
+
else
|
1454
|
+
print cyan,"Cart is empty","\n",reset
|
1455
|
+
print reset,"\n"
|
1456
|
+
end
|
1457
|
+
end
|
1458
|
+
|
1459
|
+
def format_job_execution_status(execution, return_color=cyan)
|
1460
|
+
out = ""
|
1461
|
+
status_string = execution['status'].to_s.downcase
|
1462
|
+
if status_string
|
1463
|
+
if ['complete','success', 'successful', 'ok'].include?(status_string)
|
1464
|
+
out << "#{green}#{status_string.upcase}"
|
1465
|
+
elsif ['error', 'offline', 'failed', 'failure'].include?(status_string)
|
1466
|
+
out << "#{red}#{status_string.upcase}"
|
1467
|
+
else
|
1468
|
+
out << "#{yellow}#{status_string.upcase}"
|
1469
|
+
end
|
1470
|
+
end
|
1471
|
+
out + return_color
|
1472
|
+
end
|
1473
|
+
|
1474
|
+
end
|