morpheus-cli 4.2.21 → 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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +30 -0
  4. data/lib/morpheus/api/billing_interface.rb +34 -0
  5. data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
  6. data/lib/morpheus/api/deploy_interface.rb +1 -1
  7. data/lib/morpheus/api/deployments_interface.rb +20 -1
  8. data/lib/morpheus/api/forgot_password_interface.rb +17 -0
  9. data/lib/morpheus/api/instances_interface.rb +16 -2
  10. data/lib/morpheus/api/rest_interface.rb +0 -6
  11. data/lib/morpheus/api/roles_interface.rb +14 -0
  12. data/lib/morpheus/api/search_interface.rb +13 -0
  13. data/lib/morpheus/api/servers_interface.rb +14 -0
  14. data/lib/morpheus/api/service_catalog_interface.rb +89 -0
  15. data/lib/morpheus/api/usage_interface.rb +18 -0
  16. data/lib/morpheus/cli.rb +7 -3
  17. data/lib/morpheus/cli/apps.rb +6 -27
  18. data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
  19. data/lib/morpheus/cli/backups_command.rb +3 -0
  20. data/lib/morpheus/cli/catalog_item_types_command.rb +622 -0
  21. data/lib/morpheus/cli/cli_command.rb +70 -21
  22. data/lib/morpheus/cli/commands/standard/curl_command.rb +26 -12
  23. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
  24. data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
  25. data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
  26. data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
  27. data/lib/morpheus/cli/containers_command.rb +14 -24
  28. data/lib/morpheus/cli/cypher_command.rb +6 -2
  29. data/lib/morpheus/cli/deploy.rb +199 -90
  30. data/lib/morpheus/cli/deployments.rb +341 -28
  31. data/lib/morpheus/cli/deploys.rb +206 -41
  32. data/lib/morpheus/cli/error_handler.rb +7 -0
  33. data/lib/morpheus/cli/forgot_password.rb +133 -0
  34. data/lib/morpheus/cli/groups.rb +1 -1
  35. data/lib/morpheus/cli/health_command.rb +59 -2
  36. data/lib/morpheus/cli/hosts.rb +265 -34
  37. data/lib/morpheus/cli/instances.rb +186 -100
  38. data/lib/morpheus/cli/invoices_command.rb +33 -16
  39. data/lib/morpheus/cli/jobs_command.rb +28 -6
  40. data/lib/morpheus/cli/library_option_lists_command.rb +15 -7
  41. data/lib/morpheus/cli/library_option_types_command.rb +5 -2
  42. data/lib/morpheus/cli/logs_command.rb +9 -6
  43. data/lib/morpheus/cli/mixins/accounts_helper.rb +12 -7
  44. data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
  45. data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -3
  46. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  47. data/lib/morpheus/cli/mixins/print_helper.rb +46 -21
  48. data/lib/morpheus/cli/mixins/provisioning_helper.rb +100 -4
  49. data/lib/morpheus/cli/network_pools_command.rb +14 -6
  50. data/lib/morpheus/cli/option_types.rb +271 -22
  51. data/lib/morpheus/cli/ping.rb +0 -1
  52. data/lib/morpheus/cli/remote.rb +35 -12
  53. data/lib/morpheus/cli/reports_command.rb +99 -30
  54. data/lib/morpheus/cli/roles.rb +453 -113
  55. data/lib/morpheus/cli/search_command.rb +182 -0
  56. data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
  57. data/lib/morpheus/cli/service_plans_command.rb +2 -2
  58. data/lib/morpheus/cli/setup.rb +1 -1
  59. data/lib/morpheus/cli/shell.rb +33 -11
  60. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  61. data/lib/morpheus/cli/tasks.rb +29 -32
  62. data/lib/morpheus/cli/usage_command.rb +203 -0
  63. data/lib/morpheus/cli/user_settings_command.rb +1 -0
  64. data/lib/morpheus/cli/users.rb +12 -1
  65. data/lib/morpheus/cli/version.rb +1 -1
  66. data/lib/morpheus/cli/virtual_images.rb +429 -254
  67. data/lib/morpheus/cli/whoami.rb +6 -6
  68. data/lib/morpheus/cli/workflows.rb +34 -41
  69. data/lib/morpheus/formatters.rb +75 -7
  70. data/lib/morpheus/terminal.rb +6 -2
  71. metadata +14 -2
@@ -225,6 +225,9 @@ EOT
225
225
  print_dry_run @backup_jobs_interface.dry.destroy(backup_job['id'], params)
226
226
  return
227
227
  end
228
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the backup #{backup['name']}?")
229
+ return 9, "aborted command"
230
+ end
228
231
  json_response = @backup_jobs_interface.destroy(backup_job['id'], params)
229
232
  render_response(json_response, options) do
230
233
  print_green_success "Removed backup job #{backup_job['name']}"
@@ -215,6 +215,9 @@ EOT
215
215
  print_dry_run @backups_interface.dry.destroy(backup['id'], params)
216
216
  return
217
217
  end
218
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the backup #{backup['name']}?")
219
+ return 9, "aborted command"
220
+ end
218
221
  json_response = @backups_interface.destroy(backup['id'], params)
219
222
  render_response(json_response, options) do
220
223
  print_green_success "Removed backup #{backup['name']}"
@@ -0,0 +1,622 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ # CLI command self service
4
+ # UI is Tools: Self Service - Catalog
5
+ # API is /catalog-item-types and returns catalogItemTypes
6
+ class Morpheus::Cli::CatalogItemTypesCommand
7
+ include Morpheus::Cli::CliCommand
8
+ include Morpheus::Cli::LibraryHelper
9
+ include Morpheus::Cli::OptionSourceHelper
10
+
11
+ # set_command_name :'catalog-types'
12
+ set_command_name :'self-service'
13
+ set_command_description "Self Service: View and manage catalog item types"
14
+
15
+ register_subcommands :list, :get, :add, :update, :remove
16
+
17
+ def connect(opts)
18
+ @api_client = establish_remote_appliance_connection(opts)
19
+ @catalog_item_types_interface = @api_client.catalog_item_types
20
+ @option_types_interface = @api_client.option_types
21
+ end
22
+
23
+ def handle(args)
24
+ handle_subcommand(args)
25
+ end
26
+
27
+ def list(args)
28
+ options = {}
29
+ params = {}
30
+ ref_ids = []
31
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
32
+ opts.banner = subcommand_usage("[search]")
33
+ opts.on( '--enabled [on|off]', String, "Filter by enabled" ) do |val|
34
+ params['enabled'] = (val.to_s != 'false' && val.to_s != 'off')
35
+ end
36
+ opts.on( '--featured [on|off]', String, "Filter by featured" ) do |val|
37
+ params['featured'] = (val.to_s != 'false' && val.to_s != 'off')
38
+ end
39
+ build_standard_list_options(opts, options)
40
+ opts.footer = "List catalog item types."
41
+ end
42
+ optparse.parse!(args)
43
+ connect(options)
44
+ # verify_args!(args:args, optparse:optparse, count:0)
45
+ if args.count > 0
46
+ options[:phrase] = args.join(" ")
47
+ end
48
+ params.merge!(parse_list_options(options))
49
+ @catalog_item_types_interface.setopts(options)
50
+ if options[:dry_run]
51
+ print_dry_run @catalog_item_types_interface.dry.list(params)
52
+ return
53
+ end
54
+ json_response = @catalog_item_types_interface.list(params)
55
+ catalog_item_types = json_response[catalog_item_type_list_key]
56
+ render_response(json_response, options, catalog_item_type_list_key) do
57
+ print_h1 "Morpheus Catalog Item Types", parse_list_subtitles(options), options
58
+ if catalog_item_types.empty?
59
+ print cyan,"No catalog item types found.",reset,"\n"
60
+ else
61
+ list_columns = catalog_item_type_column_definitions.upcase_keys!
62
+ list_columns.delete("Blueprint")
63
+ list_columns.delete("Workflow")
64
+ list_columns.delete("Context")
65
+ #list_columns["Config"] = lambda {|it| truncate_string(it['config'], 100) }
66
+ print as_pretty_table(catalog_item_types, list_columns.upcase_keys!, options)
67
+ print_results_pagination(json_response)
68
+ end
69
+ print reset,"\n"
70
+ end
71
+ if catalog_item_types.empty?
72
+ return 1, "no catalog item types found"
73
+ else
74
+ return 0, nil
75
+ end
76
+ end
77
+
78
+ def get(args)
79
+ params = {}
80
+ options = {}
81
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
82
+ opts.banner = subcommand_usage("[type]")
83
+ opts.on( '-c', '--config', "Display raw config only. Default is YAML. Combine with -j for JSON instead." ) do
84
+ options[:show_config] = true
85
+ end
86
+ # opts.on('--no-config', "Do not display Config YAML." ) do
87
+ # options[:no_config] = true
88
+ # end
89
+ opts.on('--no-content', "Do not display Content." ) do
90
+ options[:no_content] = true
91
+ end
92
+ build_standard_get_options(opts, options)
93
+ opts.footer = <<-EOT
94
+ Get details about a specific catalog item type.
95
+ [type] is required. This is the name or id of a catalog item type.
96
+ EOT
97
+ end
98
+ optparse.parse!(args)
99
+ verify_args!(args:args, optparse:optparse, min:1)
100
+ connect(options)
101
+ id_list = parse_id_list(args)
102
+ return run_command_for_each_arg(id_list) do |arg|
103
+ _get(arg, params, options)
104
+ end
105
+ end
106
+
107
+ def _get(id, params, options)
108
+ catalog_item_type = nil
109
+ if id.to_s !~ /\A\d{1,}\Z/
110
+ catalog_item_type = find_catalog_item_type_by_name(id)
111
+ return 1, "catalog item type not found for #{id}" if catalog_item_type.nil?
112
+ id = catalog_item_type['id']
113
+ end
114
+ @catalog_item_types_interface.setopts(options)
115
+ if options[:dry_run]
116
+ print_dry_run @catalog_item_types_interface.dry.get(id, params)
117
+ return
118
+ end
119
+ # skip extra query, list has same data as show right now
120
+ if catalog_item_type
121
+ json_response = {catalog_item_type_object_key => catalog_item_type}
122
+ else
123
+ json_response = @catalog_item_types_interface.get(id, params)
124
+ end
125
+ catalog_item_type = json_response[catalog_item_type_object_key]
126
+ config = catalog_item_type['config'] || {}
127
+ # export just the config as json or yaml (default)
128
+ if options[:show_config]
129
+ unless options[:json] || options[:yaml] || options[:csv]
130
+ options[:yaml] = true
131
+ end
132
+ return render_with_format(config, options)
133
+ end
134
+ render_response(json_response, options, catalog_item_type_object_key) do
135
+ print_h1 "Catalog Item Type Details", [], options
136
+ print cyan
137
+ show_columns = catalog_item_type_column_definitions
138
+ show_columns.delete("Blueprint") unless catalog_item_type['blueprint']
139
+ show_columns.delete("Workflow") unless catalog_item_type['workflow']
140
+ show_columns.delete("Context") unless catalog_item_type['context'] # workflow context
141
+ print_description_list(show_columns, catalog_item_type)
142
+
143
+ if catalog_item_type['optionTypes'] && catalog_item_type['optionTypes'].size > 0
144
+ print_h2 "Option Types"
145
+ opt_columns = [
146
+ {"ID" => lambda {|it| it['id'] } },
147
+ {"NAME" => lambda {|it| it['name'] } },
148
+ {"TYPE" => lambda {|it| it['type'] } },
149
+ {"FIELD NAME" => lambda {|it| it['fieldName'] } },
150
+ {"FIELD LABEL" => lambda {|it| it['fieldLabel'] } },
151
+ {"DEFAULT" => lambda {|it| it['defaultValue'] } },
152
+ {"REQUIRED" => lambda {|it| format_boolean it['required'] } },
153
+ ]
154
+ print as_pretty_table(catalog_item_type['optionTypes'], opt_columns)
155
+ else
156
+ # print cyan,"No option types found for this catalog item.","\n",reset
157
+ end
158
+
159
+ item_type_code = catalog_item_type['type'].to_s.downcase
160
+ if options[:no_config] != true
161
+ if item_type_code == 'instance'
162
+ print_h2 "Config YAML"
163
+ if config
164
+ #print reset,(JSON.pretty_generate(config) rescue config),"\n",reset
165
+ #print reset,(as_yaml(config, options) rescue config),"\n",reset
166
+ config_string = as_yaml(config, options) rescue config
167
+ config_lines = config_string.split("\n")
168
+ config_line_count = config_lines.size
169
+ max_lines = 10
170
+ if config_lines.size > max_lines
171
+ config_string = config_lines.first(max_lines).join("\n")
172
+ config_string << "\n\n"
173
+ config_string << "#{dark}(#{(config_line_count - max_lines)} more lines were not shown, use -c to show the config)#{reset}"
174
+ #config_string << "\n"
175
+ end
176
+ # strip --- yaml header
177
+ if config_string[0..3] == "---\n"
178
+ config_string = config_string[4..-1]
179
+ end
180
+ print reset,config_string.chomp("\n"),"\n",reset
181
+ else
182
+ print reset,"(blank)","\n",reset
183
+ end
184
+ elsif item_type_code == 'blueprint' || item_type_code == 'apptemplate' || item_type_code == 'app'
185
+ print_h2 "App Spec"
186
+ if catalog_item_type['appSpec']
187
+ #print reset,(JSON.pretty_generate(config) rescue config),"\n",reset
188
+ #print reset,(as_yaml(config, options) rescue config),"\n",reset
189
+ config_string = catalog_item_type['appSpec'] || ""
190
+ config_lines = config_string.split("\n")
191
+ config_line_count = config_lines.size
192
+ max_lines = 10
193
+ if config_lines.size > max_lines
194
+ config_string = config_lines.first(max_lines).join("\n")
195
+ config_string << "\n\n"
196
+ config_string << "#{dark}(#{(config_line_count - max_lines)} more lines were not shown, use -c to show the config)#{reset}"
197
+ #config_string << "\n"
198
+ end
199
+ # strip --- yaml header
200
+ if config_string[0..3] == "---\n"
201
+ config_string = config_string[4..-1]
202
+ end
203
+ print reset,config_string.chomp("\n"),"\n",reset
204
+ else
205
+ print reset,"(blank)","\n",reset
206
+ end
207
+ elsif item_type_code == 'workflow' || item_type_code == 'operationalworkflow' || item_type_code == 'taskset'
208
+ end
209
+ end
210
+
211
+ # Content (Wiki Page)
212
+ if !catalog_item_type["content"].to_s.empty? && options[:no_content] != true
213
+ print_h2 "Content"
214
+ print reset,catalog_item_type["content"].chomp("\n"),"\n",reset
215
+ end
216
+
217
+ print reset,"\n"
218
+ end
219
+ return 0, nil
220
+ end
221
+
222
+ def add(args)
223
+ options = {}
224
+ params = {}
225
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
226
+ opts.banner = subcommand_usage("[name] [options]")
227
+ # opts.on('-t', '--type [instance|blueprint|workflow]', "Item Type, default is instance.") do |val|
228
+ # # params['type'] = val.to_s.downcase
229
+ # options[:options]['type'] = val.to_s.downcase
230
+ # end
231
+ build_option_type_options(opts, options, add_catalog_item_type_option_types)
232
+ opts.on('--config-file FILE', String, "Config from a local JSON or YAML file") do |val|
233
+ options[:config_file] = val.to_s
234
+ file_content = nil
235
+ full_filename = File.expand_path(options[:config_file])
236
+ if File.exists?(full_filename)
237
+ file_content = File.read(full_filename)
238
+ else
239
+ print_red_alert "File not found: #{full_filename}"
240
+ return 1
241
+ end
242
+ parse_result = parse_json_or_yaml(file_content)
243
+ config_map = parse_result[:data]
244
+ if config_map.nil?
245
+ # todo: bubble up JSON.parse error message
246
+ raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:err]}"
247
+ #raise_command_error "Failed to parse config as valid YAML or JSON."
248
+ else
249
+ params['config'] = config_map
250
+ options[:options]['config'] = params['config'] # or file_content
251
+ end
252
+ end
253
+ opts.on('--option-types [x,y,z]', Array, "List of Option Type IDs") do |list|
254
+ if list.nil?
255
+ params['optionTypes'] = []
256
+ else
257
+ params['optionTypes'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
258
+ end
259
+ end
260
+ opts.on('--optionTypes [x,y,z]', Array, "List of Option Type IDs") do |list|
261
+ if list.nil?
262
+ params['optionTypes'] = []
263
+ else
264
+ params['optionTypes'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
265
+ end
266
+ end
267
+ opts.add_hidden_option('--optionTypes')
268
+ build_option_type_options(opts, options, add_catalog_item_type_advanced_option_types)
269
+ build_standard_add_options(opts, options)
270
+ opts.footer = <<-EOT
271
+ Create a new catalog item type.
272
+ EOT
273
+ end
274
+ optparse.parse!(args)
275
+ verify_args!(args:args, optparse:optparse, min:0, max:1)
276
+ options[:options]['name'] = args[0] if args[0]
277
+ connect(options)
278
+ payload = {}
279
+ if options[:payload]
280
+ payload = options[:payload]
281
+ payload.deep_merge!({catalog_item_type_object_key => parse_passed_options(options)})
282
+ else
283
+ payload.deep_merge!({catalog_item_type_object_key => parse_passed_options(options)})
284
+ # Type prompt first
285
+ #params['type'] = Morpheus::Cli::OptionTypes.no_prompt([{'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => [{'name' => 'Instance', 'value' => 'instance'}, {'name' => 'Blueprint', 'value' => 'blueprint'}, {'name' => 'Workflow', 'value' => 'workflow'}], 'defaultValue' => 'instance', 'required' => true}], options[:options], @api_client, options[:params])['type']
286
+ v_prompt = Morpheus::Cli::OptionTypes.prompt(add_catalog_item_type_option_types(), options[:options], @api_client, options[:params])
287
+ params.deep_merge!(v_prompt)
288
+ advanced_config = Morpheus::Cli::OptionTypes.no_prompt(add_catalog_item_type_advanced_option_types, options[:options], @api_client, options[:params])
289
+ advanced_config.deep_compact!
290
+ params.deep_merge!(advanced_config)
291
+ # convert checkbox "on" and "off" to true and false
292
+ params.booleanize!
293
+ # convert type to refType until api accepts type
294
+ # if params['type'] && !params['refType']
295
+ # if params['type'].to_s.downcase == 'instance'
296
+ # params['refType'] = 'InstanceType'
297
+ # elsif params['type'].to_s.downcase == 'blueprint'
298
+ # params['refType'] = 'AppTemplate'
299
+ # elsif params['type'].to_s.downcase == 'workflow'
300
+ # params['refType'] = 'OperationalWorkflow'
301
+ # end
302
+ # end
303
+ # convert config string to a map
304
+ config = params['config']
305
+ if config && config.is_a?(String)
306
+ parse_result = parse_json_or_yaml(config)
307
+ config_map = parse_result[:data]
308
+ if config_map.nil?
309
+ # todo: bubble up JSON.parse error message
310
+ raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:err]}"
311
+ #raise_command_error "Failed to parse config as valid YAML or JSON."
312
+ else
313
+ params['config'] = config_map
314
+ end
315
+ end
316
+ # massage association params a bit
317
+ params['workflow'] = {'id' => params['workflow']} if params['workflow'] && !params['workflow'].is_a?(Hash)
318
+ params['blueprint'] = {'id' => params['blueprint']} if params['blueprint'] && !params['blueprint'].is_a?(Hash)
319
+ prompt_results = prompt_for_option_types(params, options, @api_client)
320
+ if prompt_results[:success]
321
+ params['optionTypes'] = prompt_results[:data] unless prompt_results[:data].nil?
322
+ else
323
+ return 1, "failed to parse optionTypes"
324
+ end
325
+ payload[catalog_item_type_object_key].deep_merge!(params)
326
+ end
327
+ @catalog_item_types_interface.setopts(options)
328
+ if options[:dry_run]
329
+ print_dry_run @catalog_item_types_interface.dry.create(payload)
330
+ return 0, nil
331
+ end
332
+ json_response = @catalog_item_types_interface.create(payload)
333
+ catalog_item_type = json_response[catalog_item_type_object_key]
334
+ render_response(json_response, options, catalog_item_type_object_key) do
335
+ print_green_success "Added catalog item type #{catalog_item_type['name']}"
336
+ return _get(catalog_item_type["id"], {}, options)
337
+ end
338
+ return 0, nil
339
+ end
340
+
341
+ def update(args)
342
+ options = {}
343
+ params = {}
344
+ payload = {}
345
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
346
+ opts.banner = subcommand_usage("[type] [options]")
347
+ build_option_type_options(opts, options, update_catalog_item_type_option_types)
348
+ opts.on('--config-file FILE', String, "Config from a local JSON or YAML file") do |val|
349
+ options[:config_file] = val.to_s
350
+ file_content = nil
351
+ full_filename = File.expand_path(options[:config_file])
352
+ if File.exists?(full_filename)
353
+ file_content = File.read(full_filename)
354
+ else
355
+ print_red_alert "File not found: #{full_filename}"
356
+ return 1
357
+ end
358
+ parse_result = parse_json_or_yaml(file_content)
359
+ config_map = parse_result[:data]
360
+ if config_map.nil?
361
+ # todo: bubble up JSON.parse error message
362
+ raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:err]}"
363
+ #raise_command_error "Failed to parse config as valid YAML or JSON."
364
+ else
365
+ params['config'] = config_map
366
+ options[:options]['config'] = params['config'] # or file_content
367
+ end
368
+ end
369
+ opts.on('--option-types [x,y,z]', Array, "List of Option Type IDs") do |list|
370
+ if list.nil?
371
+ params['optionTypes'] = []
372
+ else
373
+ params['optionTypes'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
374
+ end
375
+ end
376
+ opts.on('--optionTypes [x,y,z]', Array, "List of Option Type IDs") do |list|
377
+ if list.nil?
378
+ params['optionTypes'] = []
379
+ else
380
+ params['optionTypes'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
381
+ end
382
+ end
383
+ opts.add_hidden_option('--optionTypes')
384
+ build_option_type_options(opts, options, update_catalog_item_type_advanced_option_types)
385
+ build_standard_update_options(opts, options)
386
+ opts.footer = <<-EOT
387
+ Update a catalog item type.
388
+ [type] is required. This is the name or id of a catalog item type.
389
+ EOT
390
+ end
391
+ optparse.parse!(args)
392
+ verify_args!(args:args, optparse:optparse, count:1)
393
+ connect(options)
394
+ catalog_item_type = find_catalog_item_type_by_name_or_id(args[0])
395
+ return 1 if catalog_item_type.nil?
396
+ payload = {}
397
+ if options[:payload]
398
+ payload = options[:payload]
399
+ payload.deep_merge!({catalog_item_type_object_key => parse_passed_options(options)})
400
+ else
401
+ payload.deep_merge!({catalog_item_type_object_key => parse_passed_options(options)})
402
+ # do not prompt on update
403
+ v_prompt = Morpheus::Cli::OptionTypes.no_prompt(update_catalog_item_type_option_types, options[:options], @api_client, options[:params])
404
+ v_prompt.deep_compact!
405
+ params.deep_merge!(v_prompt)
406
+ advanced_config = Morpheus::Cli::OptionTypes.no_prompt(update_catalog_item_type_advanced_option_types, options[:options], @api_client, options[:params])
407
+ advanced_config.deep_compact!
408
+ params.deep_merge!(advanced_config)
409
+ # convert checkbox "on" and "off" to true and false
410
+ params.booleanize!
411
+
412
+ # convert config string to a map
413
+ config = params['config']
414
+ if config && config.is_a?(String)
415
+ parse_result = parse_json_or_yaml(config)
416
+ config_map = parse_result[:data]
417
+ if config_map.nil?
418
+ # todo: bubble up JSON.parse error message
419
+ raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:err]}"
420
+ #raise_command_error "Failed to parse config as valid YAML or JSON."
421
+ else
422
+ params['config'] = config_map
423
+ end
424
+ end
425
+ if params['optionTypes']
426
+ # todo: move to optionSource, so it will be /api/options/optionTypes lol
427
+ prompt_results = prompt_for_option_types(params, options, @api_client)
428
+ if prompt_results[:success]
429
+ params['optionTypes'] = prompt_results[:data] unless prompt_results[:data].nil?
430
+ else
431
+ return 1, "failed to parse optionTypes"
432
+ end
433
+ end
434
+ # massage association params a bit
435
+ params['workflow'] = {'id' => params['workflow']} if params['workflow'] && !params['workflow'].is_a?(Hash)
436
+ params['blueprint'] = {'id' => params['blueprint']} if params['blueprint'] && !params['blueprint'].is_a?(Hash)
437
+ payload.deep_merge!({catalog_item_type_object_key => params})
438
+ if payload[catalog_item_type_object_key].empty? # || options[:no_prompt]
439
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
440
+ end
441
+ end
442
+ @catalog_item_types_interface.setopts(options)
443
+ if options[:dry_run]
444
+ print_dry_run @catalog_item_types_interface.dry.update(catalog_item_type['id'], payload)
445
+ return
446
+ end
447
+ json_response = @catalog_item_types_interface.update(catalog_item_type['id'], payload)
448
+ catalog_item_type = json_response[catalog_item_type_object_key]
449
+ render_response(json_response, options, catalog_item_type_object_key) do
450
+ print_green_success "Updated catalog item type #{catalog_item_type['name']}"
451
+ return _get(catalog_item_type["id"], {}, options)
452
+ end
453
+ return 0, nil
454
+ end
455
+
456
+ def remove(args)
457
+ options = {}
458
+ params = {}
459
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
460
+ opts.banner = subcommand_usage("[type] [options]")
461
+ build_standard_remove_options(opts, options)
462
+ opts.footer = <<-EOT
463
+ Delete a catalog item type.
464
+ [type] is required. This is the name or id of a catalog item type.
465
+ EOT
466
+ end
467
+ optparse.parse!(args)
468
+ verify_args!(args:args, optparse:optparse, count:1)
469
+ connect(options)
470
+ catalog_item_type = find_catalog_item_type_by_name_or_id(args[0])
471
+ return 1 if catalog_item_type.nil?
472
+ @catalog_item_types_interface.setopts(options)
473
+ if options[:dry_run]
474
+ print_dry_run @catalog_item_types_interface.dry.destroy(catalog_item_type['id'], params)
475
+ return
476
+ end
477
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the catalog item type #{catalog_item_type['name']}?")
478
+ return 9, "aborted command"
479
+ end
480
+ json_response = @catalog_item_types_interface.destroy(catalog_item_type['id'], params)
481
+ render_response(json_response, options) do
482
+ print_green_success "Removed catalog item type #{catalog_item_type['name']}"
483
+ end
484
+ return 0, nil
485
+ end
486
+
487
+ private
488
+
489
+ def catalog_item_type_column_definitions()
490
+ {
491
+ "ID" => 'id',
492
+ "Name" => 'name',
493
+ "Description" => 'description',
494
+ "Type" => lambda {|it| format_catalog_type(it) },
495
+ "Blueprint" => lambda {|it| it['blueprint'] ? it['blueprint']['name'] : nil },
496
+ "Workflow" => lambda {|it| it['workflow'] ? it['workflow']['name'] : nil },
497
+ "Context" => lambda {|it| it['context'] },
498
+ # "Content" => lambda {|it| it['content'] },
499
+ "Enabled" => lambda {|it| format_boolean(it['enabled']) },
500
+ "Featured" => lambda {|it| format_boolean(it['featured']) },
501
+ #"Config" => lambda {|it| it['config'] },
502
+ "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
503
+ "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
504
+ }
505
+ end
506
+
507
+ def format_catalog_type(catalog_item_type)
508
+ out = ""
509
+ # api "blueprint": {"name":my blueprint"} }
510
+ # instead of cryptic refType
511
+ if catalog_item_type['type']
512
+ if catalog_item_type['type'].is_a?(String)
513
+ out << catalog_item_type['type'].to_s.capitalize
514
+ else
515
+ out << (catalog_item_type['type']['name'] || catalog_item_type['type']['code']) rescue catalog_item_type['type'].to_s
516
+ end
517
+ else
518
+ # refType is not returned
519
+ ref_type = catalog_item_type['refType']
520
+ if ref_type == 'InstanceType'
521
+ out << "Instance"
522
+ elsif ref_type == 'AppTemplate'
523
+ out << "Blueprint"
524
+ elsif ref_type
525
+ out << ref_type
526
+ else
527
+ "(none)"
528
+ end
529
+ end
530
+ out
531
+ end
532
+
533
+ def add_catalog_item_type_option_types
534
+ [
535
+ {'code' => 'catalogItemType.type', 'shorthand' => '-t', 'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => [{'name' => 'Instance', 'value' => 'instance'}, {'name' => 'Blueprint', 'value' => 'blueprint'}, {'name' => 'Workflow', 'value' => 'workflow'}], 'defaultValue' => 'instance', 'required' => true},
536
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
537
+ {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text'},
538
+ {'fieldName' => 'enabled', 'fieldLabel' => 'Enabled', 'type' => 'checkbox', 'defaultValue' => true},
539
+ {'fieldName' => 'featured', 'fieldLabel' => 'Featured', 'type' => 'checkbox', 'defaultValue' => false},
540
+ {'fieldName' => 'visibility', 'fieldLabel' => 'Visibility', 'type' => 'select', 'selectOptions' => [{'name' => 'Private', 'value' => 'private'}, {'name' => 'Public', 'value' => 'public'}], 'defaultValue' => 'private', 'required' => true},
541
+ {'fieldName' => 'iconPath', 'fieldLabel' => 'Logo', 'type' => 'select', 'optionSource' => 'iconList'},
542
+ #{'fieldName' => 'optionTypes', 'fieldLabel' => 'Option Types', 'type' => 'text', 'description' => 'Option Types to include, comma separated list of names or IDs.'},
543
+ {'dependsOnCode' => 'catalogItemType.type:instance', 'fieldName' => 'config', 'fieldLabel' => 'Config', 'type' => 'code-editor', 'description' => 'JSON or YAML', 'required' => true},
544
+ {'dependsOnCode' => 'catalogItemType.type:blueprint', 'fieldName' => 'blueprint', 'fieldLabel' => 'Blueprint', 'type' => 'select', 'optionSource' => 'blueprints', 'description' => 'Choose a blueprint to apply to the catalog item.', 'required' => true, 'noParams' => true},
545
+ {'dependsOnCode' => 'catalogItemType.type:blueprint', 'fieldName' => 'appSpec', 'fieldLabel' => 'App Spec', 'type' => 'code-editor', 'description' => 'Enter a spec in the for the App, the Scribe YAML format', 'required' => true},
546
+ {'dependsOnCode' => 'catalogItemType.type:workflow', 'fieldName' => 'workflow', 'fieldLabel' => 'Workflow', 'type' => 'select', 'optionSource' => 'operationWorkflows', 'description' => 'Enter a spec in the for the App, the Scribe YAML format', 'noParams' => true},
547
+ {'dependsOnCode' => 'catalogItemType.type:workflow', 'fieldName' => 'context', 'fieldLabel' => 'Context Type', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
548
+ [{'name' => "Select", 'value' => ""}, {'name' => "None", 'value' => "appliance"}, {'name' => "Instance", 'value' => "instance"}, {'name' => "Server", 'value' => "server"}]
549
+ }, 'description' => 'Context for operational workflow, determines target type', 'defaultValue' => 'Select', 'required' => false},
550
+ {'fieldName' => 'content', 'fieldLabel' => 'Content', 'type' => 'code-editor', 'description' => 'Wiki Page Content describing the catalog item'}
551
+ ]
552
+ end
553
+
554
+ def add_catalog_item_type_advanced_option_types
555
+ []
556
+ end
557
+
558
+ def update_catalog_item_type_option_types
559
+ list = add_catalog_item_type_option_types.collect {|it|
560
+ it.delete('required')
561
+ it.delete('defaultValue')
562
+ it
563
+ }
564
+ list = list.reject {|it| ["type"].include? it['fieldName'] }
565
+ list
566
+ end
567
+
568
+ def update_catalog_item_type_advanced_option_types
569
+ add_catalog_item_type_advanced_option_types.collect {|it|
570
+ it.delete('required')
571
+ it.delete('defaultValue')
572
+ it
573
+ }
574
+ end
575
+
576
+ def catalog_item_type_object_key
577
+ 'catalogItemType'
578
+ end
579
+
580
+ def catalog_item_type_list_key
581
+ 'catalogItemTypes'
582
+ end
583
+
584
+ def find_catalog_item_type_by_name_or_id(val)
585
+ if val.to_s =~ /\A\d{1,}\Z/
586
+ return find_catalog_item_type_by_id(val)
587
+ else
588
+ return find_catalog_item_type_by_name(val)
589
+ end
590
+ end
591
+
592
+ def find_catalog_item_type_by_id(id)
593
+ begin
594
+ json_response = @catalog_item_types_interface.get(id.to_i)
595
+ return json_response[catalog_item_type_object_key]
596
+ rescue RestClient::Exception => e
597
+ if e.response && e.response.code == 404
598
+ print_red_alert "catalog item type not found by id '#{id}'"
599
+ else
600
+ raise e
601
+ end
602
+ end
603
+ end
604
+
605
+ def find_catalog_item_type_by_name(name)
606
+ json_response = @catalog_item_types_interface.list({name: name.to_s})
607
+ catalog_item_types = json_response[catalog_item_type_list_key]
608
+ if catalog_item_types.empty?
609
+ print_red_alert "catalog item type not found by name '#{name}'"
610
+ return nil
611
+ elsif catalog_item_types.size > 1
612
+ print_red_alert "#{catalog_item_types.size} catalog item types found by name '#{name}'"
613
+ puts_error as_pretty_table(catalog_item_types, [:id, :name], {color:red})
614
+ print_red_alert "Try using ID instead"
615
+ print reset,"\n"
616
+ return nil
617
+ else
618
+ return catalog_item_types[0]
619
+ end
620
+ end
621
+
622
+ end