morpheus-cli 4.2.22 → 5.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Dockerfile +1 -1
  4. data/lib/morpheus/api/api_client.rb +30 -0
  5. data/lib/morpheus/api/billing_interface.rb +34 -0
  6. data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
  7. data/lib/morpheus/api/deploy_interface.rb +1 -1
  8. data/lib/morpheus/api/deployments_interface.rb +20 -1
  9. data/lib/morpheus/api/forgot_password_interface.rb +17 -0
  10. data/lib/morpheus/api/instances_interface.rb +16 -2
  11. data/lib/morpheus/api/rest_interface.rb +0 -6
  12. data/lib/morpheus/api/roles_interface.rb +14 -0
  13. data/lib/morpheus/api/search_interface.rb +13 -0
  14. data/lib/morpheus/api/servers_interface.rb +14 -0
  15. data/lib/morpheus/api/service_catalog_interface.rb +89 -0
  16. data/lib/morpheus/api/usage_interface.rb +18 -0
  17. data/lib/morpheus/cli.rb +7 -3
  18. data/lib/morpheus/cli/apps.rb +6 -27
  19. data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
  20. data/lib/morpheus/cli/backups_command.rb +3 -0
  21. data/lib/morpheus/cli/catalog_item_types_command.rb +622 -0
  22. data/lib/morpheus/cli/cli_command.rb +70 -21
  23. data/lib/morpheus/cli/commands/standard/curl_command.rb +3 -5
  24. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
  25. data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
  26. data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
  27. data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
  28. data/lib/morpheus/cli/containers_command.rb +14 -24
  29. data/lib/morpheus/cli/cypher_command.rb +6 -2
  30. data/lib/morpheus/cli/deploy.rb +199 -90
  31. data/lib/morpheus/cli/deployments.rb +341 -28
  32. data/lib/morpheus/cli/deploys.rb +206 -41
  33. data/lib/morpheus/cli/error_handler.rb +7 -0
  34. data/lib/morpheus/cli/forgot_password.rb +133 -0
  35. data/lib/morpheus/cli/groups.rb +1 -1
  36. data/lib/morpheus/cli/health_command.rb +59 -2
  37. data/lib/morpheus/cli/hosts.rb +295 -35
  38. data/lib/morpheus/cli/instances.rb +247 -130
  39. data/lib/morpheus/cli/invoices_command.rb +37 -19
  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 +108 -5
  49. data/lib/morpheus/cli/option_types.rb +271 -22
  50. data/lib/morpheus/cli/ping.rb +0 -1
  51. data/lib/morpheus/cli/remote.rb +35 -12
  52. data/lib/morpheus/cli/reports_command.rb +99 -30
  53. data/lib/morpheus/cli/roles.rb +453 -113
  54. data/lib/morpheus/cli/search_command.rb +182 -0
  55. data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
  56. data/lib/morpheus/cli/setup.rb +1 -1
  57. data/lib/morpheus/cli/shell.rb +33 -11
  58. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  59. data/lib/morpheus/cli/tasks.rb +29 -32
  60. data/lib/morpheus/cli/usage_command.rb +203 -0
  61. data/lib/morpheus/cli/user_settings_command.rb +1 -0
  62. data/lib/morpheus/cli/users.rb +12 -1
  63. data/lib/morpheus/cli/version.rb +1 -1
  64. data/lib/morpheus/cli/virtual_images.rb +429 -254
  65. data/lib/morpheus/cli/whoami.rb +6 -6
  66. data/lib/morpheus/cli/workflows.rb +33 -40
  67. data/lib/morpheus/formatters.rb +75 -7
  68. data/lib/morpheus/terminal.rb +6 -2
  69. 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