morpheus-cli 4.2.22 → 5.0.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +14 -0
  4. data/lib/morpheus/api/billing_interface.rb +33 -0
  5. data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
  6. data/lib/morpheus/api/rest_interface.rb +0 -6
  7. data/lib/morpheus/api/roles_interface.rb +14 -0
  8. data/lib/morpheus/cli.rb +2 -2
  9. data/lib/morpheus/cli/apps.rb +3 -4
  10. data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
  11. data/lib/morpheus/cli/backups_command.rb +3 -0
  12. data/lib/morpheus/cli/catalog_command.rb +507 -0
  13. data/lib/morpheus/cli/cli_command.rb +19 -11
  14. data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
  15. data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
  16. data/lib/morpheus/cli/containers_command.rb +14 -0
  17. data/lib/morpheus/cli/hosts.rb +30 -2
  18. data/lib/morpheus/cli/instances.rb +19 -1
  19. data/lib/morpheus/cli/library_option_lists_command.rb +14 -6
  20. data/lib/morpheus/cli/mixins/accounts_helper.rb +7 -6
  21. data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
  22. data/lib/morpheus/cli/mixins/catalog_helper.rb +66 -0
  23. data/lib/morpheus/cli/mixins/deployments_helper.rb +0 -1
  24. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  25. data/lib/morpheus/cli/mixins/print_helper.rb +46 -0
  26. data/lib/morpheus/cli/ping.rb +0 -1
  27. data/lib/morpheus/cli/remote.rb +0 -2
  28. data/lib/morpheus/cli/roles.rb +305 -3
  29. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  30. data/lib/morpheus/cli/usage_command.rb +150 -0
  31. data/lib/morpheus/cli/user_settings_command.rb +1 -0
  32. data/lib/morpheus/cli/users.rb +12 -1
  33. data/lib/morpheus/cli/version.rb +1 -1
  34. data/lib/morpheus/formatters.rb +26 -5
  35. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1e56f81e53557586c3a19751c97163f5c63ce1bda3ea04e5eadd54d7e1c08e0
4
- data.tar.gz: 24c543afa2832100919d5451b8d8954952132db4b421cffe889882b5f7ef34e9
3
+ metadata.gz: b7e5399fa687da8ee714dc1bfe80cd72c2f01bf15cb465f1e6e576df53aa0a84
4
+ data.tar.gz: d5fe9c71d49308c5f7f92e47b134647712c302f833d7e77a55431bcec2e3036f
5
5
  SHA512:
6
- metadata.gz: 682d8dafc50d15c216470971f5c78ca09cba8feafa2d7f1cf0462a6368f88377a627411ba3d4287ff1669d321153be02f4db2e955606620d6a5bf8425b055e23
7
- data.tar.gz: 60b6787aacf5b2275197c496a3ff9bc9018fe70e897def95bacb54268dbdf97d5dc362e3d8f7bbfb7b0db0b18cc8028ae99b2e1f2540bb821485c21ee27b945d
6
+ metadata.gz: a0904e36c4240d638d87862cefffd681241868a8cd06730eef6ddac921d4fb288a27043d365b4ba94b3223fa6be41b783f2b9aaa570e490b731528c6b282b5ff
7
+ data.tar.gz: b05b484d490e4829e20bba8d942acef387346cc03e214cd1c04d96a2cfca603d971d20054cb3bb9f9024d5bf977864b543d3c6d599089765f55426aa317a4f0e
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  FROM ruby:2.5.1
2
2
 
3
- RUN gem install morpheus-cli -v 4.2.22
3
+ RUN gem install morpheus-cli -v 5.0.0
4
4
 
5
5
  ENTRYPOINT ["morpheus"]
@@ -764,6 +764,20 @@ class Morpheus::APIClient
764
764
  Morpheus::BackupJobsInterface.new(common_interface_options).setopts(@options)
765
765
  end
766
766
 
767
+ def catalog_item_types
768
+ Morpheus::CatalogItemTypesInterface.new(common_interface_options).setopts(@options)
769
+ end
770
+
771
+ def billing
772
+ Morpheus::BillingInterface.new(common_interface_options).setopts(@options)
773
+ end
774
+
767
775
  # add new interfaces here
768
776
 
777
+ protected
778
+
779
+ def validate_id!(id)
780
+ raise "#{self.class} passed a blank id!" if id.to_s.strip.empty?
781
+ end
782
+
769
783
  end
@@ -0,0 +1,33 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::BillingInterface < Morpheus::APIClient
4
+
5
+ def base_path
6
+ "/api/billing"
7
+ end
8
+
9
+ def list(params={})
10
+ execute(method: :get, url: "#{base_path}", params: params)
11
+ end
12
+
13
+ def list_account(params={})
14
+ execute(method: :get, url: "#{base_path}/account", params: params)
15
+ end
16
+
17
+ def list_zones(params={})
18
+ execute(method: :get, url: "#{base_path}/zones", params: params)
19
+ end
20
+
21
+ def list_instances(params={})
22
+ execute(method: :get, url: "#{base_path}/instances", params: params)
23
+ end
24
+
25
+ def list_servers(params={})
26
+ execute(method: :get, url: "#{base_path}/servers", params: params)
27
+ end
28
+
29
+ def list_discovered_servers(params={})
30
+ execute(method: :get, url: "#{base_path}/discoveredServers", params: params)
31
+ end
32
+
33
+ end
@@ -0,0 +1,9 @@
1
+ require 'morpheus/api/rest_interface'
2
+
3
+ class Morpheus::CatalogItemTypesInterface < Morpheus::RestInterface
4
+
5
+ def base_path
6
+ "/api/catalog-item-types"
7
+ end
8
+
9
+ end
@@ -31,10 +31,4 @@ class Morpheus::RestInterface < Morpheus::APIClient
31
31
  execute(method: :delete, url: "#{base_path}/#{id}", params: params)
32
32
  end
33
33
 
34
- protected
35
-
36
- def validate_id!(id)
37
- raise "#{self.class} passed a blank id!" if id.to_s.strip.empty?
38
- end
39
-
40
34
  end
@@ -77,6 +77,20 @@ class Morpheus::RolesInterface < Morpheus::APIClient
77
77
  execute(method: :put, url: url, headers: headers, payload: payload.to_json)
78
78
  end
79
79
 
80
+ def update_catalog_item_type(account_id, id, options)
81
+ url = build_url(account_id, id) + "/update-catalog-item-type"
82
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
83
+ payload = options
84
+ execute(method: :put, url: url, headers: headers, payload: payload.to_json)
85
+ end
86
+
87
+ def update_persona(account_id, id, options)
88
+ url = build_url(account_id, id) + "/update-persona"
89
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
90
+ payload = options
91
+ execute(method: :put, url: url, headers: headers, payload: payload.to_json)
92
+ end
93
+
80
94
  private
81
95
 
82
96
  def build_url(account_id=nil, role_id=nil)
@@ -67,8 +67,6 @@ module Morpheus
67
67
  # all standard commands
68
68
  Dir[File.dirname(__FILE__) + "/cli/commands/standard/**/*.rb"].each {|file| load file }
69
69
 
70
- # shell scripting commands
71
-
72
70
  # all the known commands
73
71
  load 'morpheus/cli/remote.rb'
74
72
  load 'morpheus/cli/doc.rb'
@@ -174,6 +172,8 @@ module Morpheus
174
172
  load 'morpheus/cli/projects_command.rb'
175
173
  load 'morpheus/cli/backups_command.rb'
176
174
  load 'morpheus/cli/backup_jobs_command.rb'
175
+ load 'morpheus/cli/catalog_command.rb'
176
+ load 'morpheus/cli/usage_command.rb'
177
177
  # add new commands here...
178
178
 
179
179
  end
@@ -92,10 +92,9 @@ class Morpheus::Cli::Apps
92
92
  opts.footer = "List apps."
93
93
  end
94
94
  optparse.parse!(args)
95
- if args.count != 0
96
- print_error Morpheus::Terminal.angry_prompt
97
- puts_error "#{command_name} list expects 0 arguments and received #{args.count}: #{args}\n#{optparse}"
98
- return 1
95
+ # verify_args!(args:args, optparse:optparse, count:0)
96
+ if args.count > 0
97
+ options[:phrase] = args.join(" ")
99
98
  end
100
99
  connect(options)
101
100
  begin
@@ -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,507 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ # CLI command self service
4
+ # UI is Tools: Self Service - Catalog Items
5
+ # API is /catalog-item-types and returns catalogItemTypes
6
+ class Morpheus::Cli::CatalogCommand
7
+ include Morpheus::Cli::CliCommand
8
+ include Morpheus::Cli::CatalogHelper
9
+ include Morpheus::Cli::LibraryHelper
10
+ include Morpheus::Cli::OptionSourceHelper
11
+
12
+ # hide until 5.1 when update api is fixed and service-catalog endpoints are available
13
+ set_command_hidden
14
+ set_command_name :'catalog'
15
+
16
+ register_subcommands :list, :get, :add, :update, :remove
17
+
18
+ def connect(opts)
19
+ @api_client = establish_remote_appliance_connection(opts)
20
+ @catalog_item_types_interface = @api_client.catalog_item_types
21
+ @option_types_interface = @api_client.option_types
22
+ end
23
+
24
+ def handle(args)
25
+ handle_subcommand(args)
26
+ end
27
+
28
+ def list(args)
29
+ options = {}
30
+ params = {}
31
+ ref_ids = []
32
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
33
+ opts.banner = subcommand_usage("[search]")
34
+ opts.on( '--enabled [on|off]', String, "Filter by enabled" ) do |val|
35
+ params['enabled'] = (val.to_s != 'false' && val.to_s != 'off')
36
+ end
37
+ opts.on( '--featured [on|off]', String, "Filter by featured" ) do |val|
38
+ params['featured'] = (val.to_s != 'false' && val.to_s != 'off')
39
+ end
40
+ build_standard_list_options(opts, options)
41
+ opts.footer = "List catalog items."
42
+ end
43
+ optparse.parse!(args)
44
+ connect(options)
45
+ # verify_args!(args:args, optparse:optparse, count:0)
46
+ if args.count > 0
47
+ options[:phrase] = args.join(" ")
48
+ end
49
+ params.merge!(parse_list_options(options))
50
+ @catalog_item_types_interface.setopts(options)
51
+ if options[:dry_run]
52
+ print_dry_run @catalog_item_types_interface.dry.list(params)
53
+ return
54
+ end
55
+ json_response = @catalog_item_types_interface.list(params)
56
+ catalog_item_types = json_response[catalog_item_type_list_key]
57
+ render_response(json_response, options, catalog_item_type_list_key) do
58
+ print_h1 "Morpheus Catalog Items", parse_list_subtitles(options), options
59
+ if catalog_item_types.empty?
60
+ print cyan,"No catalog items found.",reset,"\n"
61
+ else
62
+ list_columns = catalog_item_type_column_definitions.upcase_keys!
63
+ #list_columns["Config"] = lambda {|it| truncate_string(it['config'], 100) }
64
+ print as_pretty_table(catalog_item_types, list_columns.upcase_keys!, options)
65
+ print_results_pagination(json_response)
66
+ end
67
+ print reset,"\n"
68
+ end
69
+ if catalog_item_types.empty?
70
+ return 1, "no catalog items found"
71
+ else
72
+ return 0, nil
73
+ end
74
+ end
75
+
76
+ def get(args)
77
+ params = {}
78
+ options = {}
79
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
80
+ opts.banner = subcommand_usage("[catalog item type]")
81
+ opts.on( '-c', '--config', "Display raw config only. Default is YAML. Combine with -j for JSON instead." ) do
82
+ options[:show_config] = true
83
+ end
84
+ # opts.on('--no-config', "Do not display config content." ) do
85
+ # options[:no_config] = true
86
+ # end
87
+ build_standard_get_options(opts, options)
88
+ opts.footer = <<-EOT
89
+ Get details about a specific catalog item type.
90
+ [catalog item type] is required. This is the name or id of a catalog item type.
91
+ EOT
92
+ end
93
+ optparse.parse!(args)
94
+ verify_args!(args:args, optparse:optparse, min:1)
95
+ connect(options)
96
+ id_list = parse_id_list(args)
97
+ return run_command_for_each_arg(id_list) do |arg|
98
+ _get(arg, params, options)
99
+ end
100
+ end
101
+
102
+ def _get(id, params, options)
103
+ catalog_item_type = nil
104
+ if id.to_s !~ /\A\d{1,}\Z/
105
+ catalog_item_type = find_catalog_item_type_by_name(id)
106
+ return 1, "catalog item type not found for #{id}" if catalog_item_type.nil?
107
+ id = catalog_item_type['id']
108
+ end
109
+ @catalog_item_types_interface.setopts(options)
110
+ if options[:dry_run]
111
+ print_dry_run @catalog_item_types_interface.dry.get(id, params)
112
+ return
113
+ end
114
+ # skip extra query, list has same data as show right now
115
+ if catalog_item_type
116
+ json_response = {catalog_item_type_object_key => catalog_item_type}
117
+ else
118
+ json_response = @catalog_item_types_interface.get(id, params)
119
+ end
120
+ catalog_item_type = json_response[catalog_item_type_object_key]
121
+ config = catalog_item_type['config'] || {}
122
+ # export just the config as json or yaml (default)
123
+ if options[:show_config]
124
+ unless options[:json] || options[:yaml] || options[:csv]
125
+ options[:yaml] = true
126
+ end
127
+ return render_with_format(config, options)
128
+ end
129
+ render_response(json_response, options, catalog_item_type_object_key) do
130
+ print_h1 "Catalog Item Type Details", [], options
131
+ print cyan
132
+ show_columns = catalog_item_type_column_definitions
133
+ show_columns.delete("Blueprint") unless catalog_item_type['blueprint']
134
+ print_description_list(show_columns, catalog_item_type)
135
+
136
+ if catalog_item_type['optionTypes'] && catalog_item_type['optionTypes'].size > 0
137
+ print_h2 "Option Types"
138
+ opt_columns = [
139
+ {"ID" => lambda {|it| it['id'] } },
140
+ {"NAME" => lambda {|it| it['name'] } },
141
+ {"TYPE" => lambda {|it| it['type'] } },
142
+ {"FIELD NAME" => lambda {|it| it['fieldName'] } },
143
+ {"FIELD LABEL" => lambda {|it| it['fieldLabel'] } },
144
+ {"DEFAULT" => lambda {|it| it['defaultValue'] } },
145
+ {"REQUIRED" => lambda {|it| format_boolean it['required'] } },
146
+ ]
147
+ print as_pretty_table(catalog_item_type['optionTypes'], opt_columns)
148
+ else
149
+ # print cyan,"No option types found for this catalog item.","\n",reset
150
+ end
151
+
152
+ if config && options[:no_config] != true
153
+ print_h2 "Config YAML"
154
+ #print reset,(JSON.pretty_generate(config) rescue config),"\n",reset
155
+ #print reset,(as_yaml(config, options) rescue config),"\n",reset
156
+ config_string = as_yaml(config, options) rescue config
157
+ config_lines = config_string.split("\n")
158
+ config_line_count = config_lines.size
159
+ max_lines = 10
160
+ if config_lines.size > max_lines
161
+ config_string = config_lines.first(max_lines).join("\n")
162
+ config_string << "\n\n"
163
+ config_string << "(#{(config_line_count - max_lines)} more lines were not shown, use -c to show the config)"
164
+ #config_string << "\n"
165
+ end
166
+ # strip --- yaml header
167
+ if config_string[0..3] == "---\n"
168
+ config_string = config_string[4..-1]
169
+ end
170
+ print reset,config_string.chomp("\n"),"\n",reset
171
+ end
172
+
173
+ print reset,"\n"
174
+ end
175
+ return 0, nil
176
+ end
177
+
178
+ def add(args)
179
+ options = {}
180
+ params = {}
181
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
182
+ opts.banner = subcommand_usage("[name] [options]")
183
+ build_option_type_options(opts, options, add_catalog_item_type_option_types)
184
+ opts.on('--config-file FILE', String, "Config from a local JSON or YAML file") do |val|
185
+ options[:config_file] = val.to_s
186
+ file_content = nil
187
+ full_filename = File.expand_path(options[:config_file])
188
+ if File.exists?(full_filename)
189
+ file_content = File.read(full_filename)
190
+ else
191
+ print_red_alert "File not found: #{full_filename}"
192
+ return 1
193
+ end
194
+ parse_result = parse_json_or_yaml(file_content)
195
+ config_map = parse_result[:data]
196
+ if config_map.nil?
197
+ # todo: bubble up JSON.parse error message
198
+ raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:err]}"
199
+ #raise_command_error "Failed to parse config as valid YAML or JSON."
200
+ else
201
+ params['config'] = config_map
202
+ options[:options]['config'] = params['config'] # or file_content
203
+ end
204
+ end
205
+ opts.on('--option-types [x,y,z]', Array, "List of Option Type IDs") do |list|
206
+ if list.nil?
207
+ params['optionTypes'] = []
208
+ else
209
+ params['optionTypes'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
210
+ end
211
+ end
212
+ opts.on('--optionTypes [x,y,z]', Array, "List of Option Type IDs") do |list|
213
+ if list.nil?
214
+ params['optionTypes'] = []
215
+ else
216
+ params['optionTypes'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
217
+ end
218
+ end
219
+ opts.add_hidden_option('--optionTypes')
220
+ build_option_type_options(opts, options, add_catalog_item_type_advanced_option_types)
221
+ build_standard_add_options(opts, options)
222
+ opts.footer = <<-EOT
223
+ Create a new catalog item type.
224
+ EOT
225
+ end
226
+ optparse.parse!(args)
227
+ verify_args!(args:args, optparse:optparse, min:0, max:1)
228
+ options[:options]['name'] = args[0] if args[0]
229
+ connect(options)
230
+ payload = {}
231
+ if options[:payload]
232
+ payload = options[:payload]
233
+ payload.deep_merge!({catalog_item_type_object_key => parse_passed_options(options)})
234
+ else
235
+ payload.deep_merge!({catalog_item_type_object_key => parse_passed_options(options)})
236
+ v_prompt = Morpheus::Cli::OptionTypes.prompt(add_catalog_item_type_option_types(), options[:options], @api_client, options[:params])
237
+ params.deep_merge!(v_prompt)
238
+ advanced_config = Morpheus::Cli::OptionTypes.no_prompt(add_catalog_item_type_advanced_option_types, options[:options], @api_client, options[:params])
239
+ advanced_config.deep_compact!
240
+ params.deep_merge!(advanced_config)
241
+ # convert checkbox "on" and "off" to true and false
242
+ params.booleanize!
243
+ # convert type to refType until api accepts type
244
+ if params['type'] && !params['refType']
245
+ if params['type'].to_s.downcase == 'blueprint'
246
+ params['refType'] = 'AppTemplate'
247
+ else
248
+ params['refType'] = 'InstanceType'
249
+ end
250
+ end
251
+ # convert config string to a map
252
+ config = params['config']
253
+ if config && config.is_a?(String)
254
+ parse_result = parse_json_or_yaml(config)
255
+ config_map = parse_result[:data]
256
+ if config_map.nil?
257
+ # todo: bubble up JSON.parse error message
258
+ raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:err]}"
259
+ #raise_command_error "Failed to parse config as valid YAML or JSON."
260
+ else
261
+ params['config'] = config_map
262
+ end
263
+ end
264
+ if params['optionTypes']
265
+ # todo: move to optionSource, so it will be /api/options/optionTypes lol
266
+ prompt_results = prompt_for_option_types(params, options, @api_client)
267
+ if prompt_results[:success]
268
+ params['optionTypes'] = prompt_results[:data] unless prompt_results[:data].nil?
269
+ else
270
+ return 1
271
+ end
272
+ end
273
+ payload[catalog_item_type_object_key].deep_merge!(params)
274
+ end
275
+ @catalog_item_types_interface.setopts(options)
276
+ if options[:dry_run]
277
+ print_dry_run @catalog_item_types_interface.dry.create(payload)
278
+ return 0, nil
279
+ end
280
+ json_response = @catalog_item_types_interface.create(payload)
281
+ catalog_item_type = json_response[catalog_item_type_object_key]
282
+ render_response(json_response, options, catalog_item_type_object_key) do
283
+ print_green_success "Added catalog item #{catalog_item_type['name']}"
284
+ return _get(catalog_item_type["id"], {}, options)
285
+ end
286
+ return 0, nil
287
+ end
288
+
289
+ def update(args)
290
+ options = {}
291
+ params = {}
292
+ payload = {}
293
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
294
+ opts.banner = subcommand_usage("[catalog item type] [options]")
295
+ build_option_type_options(opts, options, update_catalog_item_type_option_types)
296
+ opts.on('--config-file FILE', String, "Config from a local JSON or YAML file") do |val|
297
+ options[:config_file] = val.to_s
298
+ file_content = nil
299
+ full_filename = File.expand_path(options[:config_file])
300
+ if File.exists?(full_filename)
301
+ file_content = File.read(full_filename)
302
+ else
303
+ print_red_alert "File not found: #{full_filename}"
304
+ return 1
305
+ end
306
+ parse_result = parse_json_or_yaml(file_content)
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
+ options[:options]['config'] = params['config'] # or file_content
315
+ end
316
+ end
317
+ opts.on('--option-types [x,y,z]', Array, "List of Option Type IDs") do |list|
318
+ if list.nil?
319
+ params['optionTypes'] = []
320
+ else
321
+ params['optionTypes'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
322
+ end
323
+ end
324
+ opts.on('--optionTypes [x,y,z]', Array, "List of Option Type IDs") do |list|
325
+ if list.nil?
326
+ params['optionTypes'] = []
327
+ else
328
+ params['optionTypes'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
329
+ end
330
+ end
331
+ opts.add_hidden_option('--optionTypes')
332
+ build_option_type_options(opts, options, update_catalog_item_type_advanced_option_types)
333
+ build_standard_update_options(opts, options)
334
+ opts.footer = <<-EOT
335
+ Update a catalog item type.
336
+ [catalog item type] is required. This is the name or id of a catalog item type.
337
+ EOT
338
+ end
339
+ optparse.parse!(args)
340
+ verify_args!(args:args, optparse:optparse, count:1)
341
+ connect(options)
342
+ catalog_item_type = find_catalog_item_type_by_name_or_id(args[0])
343
+ return 1 if catalog_item_type.nil?
344
+ payload = {}
345
+ if options[:payload]
346
+ payload = options[:payload]
347
+ payload.deep_merge!({catalog_item_type_object_key => parse_passed_options(options)})
348
+ else
349
+ payload.deep_merge!({catalog_item_type_object_key => parse_passed_options(options)})
350
+ # do not prompt on update
351
+ v_prompt = Morpheus::Cli::OptionTypes.no_prompt(update_catalog_item_type_option_types, options[:options], @api_client, options[:params])
352
+ v_prompt.deep_compact!
353
+ params.deep_merge!(v_prompt)
354
+ advanced_config = Morpheus::Cli::OptionTypes.no_prompt(update_catalog_item_type_advanced_option_types, options[:options], @api_client, options[:params])
355
+ advanced_config.deep_compact!
356
+ params.deep_merge!(advanced_config)
357
+ # convert checkbox "on" and "off" to true and false
358
+ params.booleanize!
359
+ # convert type to refType until api accepts type
360
+ if params['type'] && !params['refType']
361
+ if params['type'].to_s.downcase == 'blueprint'
362
+ params['refType'] = 'AppTemplate'
363
+ else
364
+ params['refType'] = 'InstanceType'
365
+ end
366
+ end
367
+ # convert config string to a map
368
+ config = params['config']
369
+ if config && config.is_a?(String)
370
+ parse_result = parse_json_or_yaml(config)
371
+ config_map = parse_result[:data]
372
+ if config_map.nil?
373
+ # todo: bubble up JSON.parse error message
374
+ raise_command_error "Failed to parse config as YAML or JSON. Error: #{parse_result[:err]}"
375
+ #raise_command_error "Failed to parse config as valid YAML or JSON."
376
+ else
377
+ params['config'] = config_map
378
+ end
379
+ end
380
+ payload.deep_merge!({catalog_item_type_object_key => params})
381
+ if payload[catalog_item_type_object_key].empty? # || options[:no_prompt]
382
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
383
+ end
384
+ end
385
+ @catalog_item_types_interface.setopts(options)
386
+ if options[:dry_run]
387
+ print_dry_run @catalog_item_types_interface.dry.update(catalog_item_type['id'], payload)
388
+ return
389
+ end
390
+ json_response = @catalog_item_types_interface.update(catalog_item_type['id'], payload)
391
+ catalog_item_type = json_response[catalog_item_type_object_key]
392
+ render_response(json_response, options, catalog_item_type_object_key) do
393
+ print_green_success "Updated catalog item #{catalog_item_type['name']}"
394
+ return _get(catalog_item_type["id"], {}, options)
395
+ end
396
+ return 0, nil
397
+ end
398
+
399
+ def remove(args)
400
+ options = {}
401
+ params = {}
402
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
403
+ opts.banner = subcommand_usage("[catalog item type] [options]")
404
+ build_standard_remove_options(opts, options)
405
+ opts.footer = <<-EOT
406
+ Delete a catalog_item_type.
407
+ [catalog item type] is required. This is the name or id of a catalog item type.
408
+ EOT
409
+ end
410
+ optparse.parse!(args)
411
+ verify_args!(args:args, optparse:optparse, count:1)
412
+ connect(options)
413
+ catalog_item_type = find_catalog_item_type_by_name_or_id(args[0])
414
+ return 1 if catalog_item_type.nil?
415
+ @catalog_item_types_interface.setopts(options)
416
+ if options[:dry_run]
417
+ print_dry_run @catalog_item_types_interface.dry.destroy(catalog_item_type['id'], params)
418
+ return
419
+ end
420
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the catalog item #{catalog_item_type['name']}?")
421
+ return 9, "aborted command"
422
+ end
423
+ json_response = @catalog_item_types_interface.destroy(catalog_item_type['id'], params)
424
+ render_response(json_response, options) do
425
+ print_green_success "Removed catalog item #{catalog_item_type['name']}"
426
+ end
427
+ return 0, nil
428
+ end
429
+
430
+ private
431
+
432
+ def catalog_item_type_column_definitions()
433
+ {
434
+ "ID" => 'id',
435
+ "Name" => 'name',
436
+ "Description" => 'description',
437
+ "Type" => lambda {|it| format_catalog_type(it) },
438
+ "Blueprint" => lambda {|it| it['blueprint'] ? it['blueprint']['name'] : nil },
439
+ "Enabled" => lambda {|it| format_boolean(it['enabled']) },
440
+ "Featured" => lambda {|it| format_boolean(it['featured']) },
441
+ #"Config" => lambda {|it| it['config'] },
442
+ "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
443
+ "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
444
+ }
445
+ end
446
+
447
+ def format_catalog_type(catalog_item_type)
448
+ out = ""
449
+ # api "blueprint": {"name":my blueprint"} }
450
+ # instead of cryptic refType
451
+ if catalog_item_type['type']
452
+ if catalog_item_type['type'].is_a?(String)
453
+ out << catalog_item_type['type'].to_s.capitalize
454
+ else
455
+ out << (catalog_item_type['type']['name'] || catalog_item_type['type']['code']) rescue catalog_item_type['type'].to_s
456
+ end
457
+ else
458
+ ref_type = catalog_item_type['refType']
459
+ if ref_type == 'InstanceType'
460
+ out << "Instance"
461
+ elsif ref_type == 'AppTemplate'
462
+ out << "Blueprint"
463
+ elsif ref_type
464
+ out << ref_type
465
+ else
466
+ "(none)"
467
+ end
468
+ end
469
+ out
470
+ end
471
+
472
+ # this is not so simple, need to first choose select instance, host or provider
473
+ def add_catalog_item_type_option_types
474
+ [
475
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
476
+ {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text'},
477
+ {'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => [{'name' => 'Instance', 'value' => 'instance'}, {'name' => 'Blueprint', 'value' => 'blueprint'}], 'defaultValue' => 'instance'},
478
+ {'fieldName' => 'enabled', 'fieldLabel' => 'Enabled', 'type' => 'checkbox', 'defaultValue' => true},
479
+ {'fieldName' => 'featured', 'fieldLabel' => 'Featured', 'type' => 'checkbox', 'defaultValue' => false},
480
+ {'fieldName' => 'visibility', 'fieldLabel' => 'Visibility', 'type' => 'select', 'selectOptions' => [{'name' => 'Private', 'value' => 'private'}, {'name' => 'Public', 'value' => 'public'}], 'defaultValue' => 'private', 'required' => true},
481
+ {'fieldName' => 'iconPath', 'fieldLabel' => 'Logo', 'type' => 'select', 'optionSource' => 'iconList'},
482
+ #{'fieldName' => 'optionTypes', 'fieldLabel' => 'Option Types', 'type' => 'text', 'description' => 'Option Types to include, comma separated list of names or IDs.'},
483
+ {'fieldName' => 'config', 'fieldLabel' => 'Config', 'type' => 'code-editor', 'required' => true, 'description' => 'JSON or YAML'}
484
+ ]
485
+ end
486
+
487
+ def add_catalog_item_type_advanced_option_types
488
+ []
489
+ end
490
+
491
+ def update_catalog_item_type_option_types
492
+ add_catalog_item_type_option_types.collect {|it|
493
+ it.delete('required')
494
+ it.delete('defaultValue')
495
+ it
496
+ }
497
+ end
498
+
499
+ def update_catalog_item_type_advanced_option_types
500
+ add_catalog_item_type_advanced_option_types.collect {|it|
501
+ it.delete('required')
502
+ it.delete('defaultValue')
503
+ it
504
+ }
505
+ end
506
+
507
+ end