morpheus-cli 4.2.18 → 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 (51) 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/blueprints_command.rb +27 -61
  13. data/lib/morpheus/cli/budgets_command.rb +4 -4
  14. data/lib/morpheus/cli/catalog_command.rb +507 -0
  15. data/lib/morpheus/cli/cli_command.rb +30 -15
  16. data/lib/morpheus/cli/cloud_resource_pools_command.rb +16 -0
  17. data/lib/morpheus/cli/clouds.rb +7 -10
  18. data/lib/morpheus/cli/commands/standard/curl_command.rb +23 -7
  19. data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
  20. data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
  21. data/lib/morpheus/cli/containers_command.rb +14 -0
  22. data/lib/morpheus/cli/deployments.rb +1 -1
  23. data/lib/morpheus/cli/hosts.rb +30 -2
  24. data/lib/morpheus/cli/instances.rb +19 -1
  25. data/lib/morpheus/cli/invoices_command.rb +8 -9
  26. data/lib/morpheus/cli/jobs_command.rb +30 -8
  27. data/lib/morpheus/cli/library_instance_types_command.rb +17 -3
  28. data/lib/morpheus/cli/library_option_lists_command.rb +14 -6
  29. data/lib/morpheus/cli/mixins/accounts_helper.rb +7 -6
  30. data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
  31. data/lib/morpheus/cli/mixins/catalog_helper.rb +66 -0
  32. data/lib/morpheus/cli/mixins/deployments_helper.rb +0 -1
  33. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  34. data/lib/morpheus/cli/mixins/print_helper.rb +46 -0
  35. data/lib/morpheus/cli/network_pools_command.rb +14 -6
  36. data/lib/morpheus/cli/ping.rb +0 -1
  37. data/lib/morpheus/cli/projects_command.rb +7 -7
  38. data/lib/morpheus/cli/provisioning_licenses_command.rb +2 -2
  39. data/lib/morpheus/cli/remote.rb +0 -2
  40. data/lib/morpheus/cli/roles.rb +305 -3
  41. data/lib/morpheus/cli/service_plans_command.rb +2 -2
  42. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  43. data/lib/morpheus/cli/tasks.rb +24 -10
  44. data/lib/morpheus/cli/tenants_command.rb +1 -1
  45. data/lib/morpheus/cli/usage_command.rb +150 -0
  46. data/lib/morpheus/cli/user_settings_command.rb +1 -0
  47. data/lib/morpheus/cli/users.rb +12 -1
  48. data/lib/morpheus/cli/version.rb +1 -1
  49. data/lib/morpheus/cli/workflows.rb +12 -9
  50. data/lib/morpheus/formatters.rb +26 -5
  51. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 625ac78e3858476f22af0b74911f623680b61a1f02808d7af341a49e591bd679
4
- data.tar.gz: 9ba142c3499a5fbb16b5e920c19c2fc3efa0c3617b3d29b4766bc960f6372a51
3
+ metadata.gz: b7e5399fa687da8ee714dc1bfe80cd72c2f01bf15cb465f1e6e576df53aa0a84
4
+ data.tar.gz: d5fe9c71d49308c5f7f92e47b134647712c302f833d7e77a55431bcec2e3036f
5
5
  SHA512:
6
- metadata.gz: 9ab2c8609c0fbc67d11a59db49b7dfb19b6f6bac97cea016797d5d43e56a620d74e0c2d125234aef996e6900afbab6258b7a069f6c36984f31760acd22023f54
7
- data.tar.gz: eddf3b6dca658ee215313f7970af3377ad6c284bf127869b2b8c4ddeb212081dead70a909d6ee1740aba849a415e8d64c49859ecfba160fed2f36d395e447197
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.18
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']}"
@@ -62,11 +62,15 @@ class Morpheus::Cli::BlueprintsCommand
62
62
  params['ownerId'] << val
63
63
  end
64
64
  opts.add_hidden_option('--created-by')
65
- build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
65
+ build_standard_list_options(opts, options)
66
66
  opts.footer = "List blueprints."
67
67
  end
68
68
  optparse.parse!(args)
69
69
  connect(options)
70
+ # verify_args!(args:args, optparse:optparse, count:0)
71
+ if args.count > 0
72
+ options[:phrase] = args.join(" ")
73
+ end
70
74
  begin
71
75
  if params['ownerId']
72
76
  params['ownerId'] = params['ownerId'].collect do |owner_id|
@@ -125,7 +129,7 @@ class Morpheus::Cli::BlueprintsCommand
125
129
  opts.on( '-c', '--config', "Display raw config only. Default is YAML. Combine with -j for JSON instead." ) do
126
130
  options[:show_config] = true
127
131
  end
128
- build_common_options(opts, options, [:json, :yaml, :csv, :fields, :outfile, :dry_run, :remote])
132
+ build_standard_get_options(opts, options)
129
133
  opts.footer = "Get details about a blueprint.\n" +
130
134
  "[blueprint] is required. This is the name or id of a blueprint. Supports 1-N [instance] arguments."
131
135
  end
@@ -154,8 +158,9 @@ class Morpheus::Cli::BlueprintsCommand
154
158
  end
155
159
  @blueprints_interface.setopts(options)
156
160
  blueprint = find_blueprint_by_name_or_id(arg)
157
- exit 1 if blueprint.nil?
158
-
161
+ if blueprint.nil?
162
+ return 1, "blueprint not found"
163
+ end
159
164
  json_response = {'blueprint' => blueprint} # skip redundant request
160
165
  #json_response = @blueprints_interface.get(blueprint['id'])
161
166
  blueprint = json_response['blueprint']
@@ -165,43 +170,13 @@ class Morpheus::Cli::BlueprintsCommand
165
170
  unless options[:json] || options[:yaml] || options[:csv]
166
171
  options[:yaml] = true
167
172
  end
168
- blueprint_config = blueprint['config']
169
- render_result = render_with_format(blueprint_config, options)
170
- return 0 if render_result
173
+ return render_with_format(blueprint['config'], options)
171
174
  end
172
-
173
- render_result = render_with_format(json_response, options, 'blueprint')
174
- return 0 if render_result
175
-
176
- print_h1 "Blueprint Details"
177
-
178
- print_blueprint_details(blueprint)
179
-
180
- if blueprint['resourcePermission'].nil?
181
- #print "\n", "No group access found", "\n"
182
- else
183
- # print_h2 "Group Access"
184
- # rows = []
185
- # if blueprint['resourcePermission']['allSites'] || blueprint['resourcePermission']['all']
186
- # rows.push({"name" => 'All'})
187
- # end
188
- # if blueprint['resourcePermission']['sites']
189
- # blueprint['resourcePermission']['sites'].each do |site|
190
- # rows.push(site)
191
- # end
192
- # end
193
- # rows = rows.collect do |site|
194
- # {group: site['name'], default: site['default'] ? 'Yes' : ''}
195
- # end
196
- # # columns = [:group, :default]
197
- # columns = [:group]
198
- # print cyan
199
- # print as_pretty_table(rows, columns)
200
- # print reset,"\n"
201
- end
202
-
203
- #print reset,"\n"
204
- return 0
175
+ render_response(json_response, options, 'blueprint') do
176
+ print_h1 "Blueprint Details"
177
+ print_blueprint_details(blueprint)
178
+ end
179
+ return 0, nil
205
180
  rescue RestClient::Exception => e
206
181
  print_rest_exception(e, options)
207
182
  exit 1
@@ -216,17 +191,12 @@ class Morpheus::Cli::BlueprintsCommand
216
191
  opts.on('-t', '--type TYPE', String, "Blueprint Type. Default is morpheus.") do |val|
217
192
  options[:blueprint_type] = parse_blueprint_type(val.to_s)
218
193
  end
219
- build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
194
+ build_standard_add_options(opts, options)
220
195
  opts.footer = "Create a new blueprint.\n" +
221
196
  "[name] is required. This is the name of the new blueprint."
222
197
  end
223
198
  optparse.parse!(args)
224
- if args.count > 1
225
- print_error Morpheus::Terminal.angry_prompt
226
- puts_error "#{command_name} add expects 0-1 arguments and received #{args.count}: #{args}\n#{optparse}"
227
- return 1
228
- end
229
- options[:options] ||= {}
199
+ verify_args!(args:args, optparse:optparse, min:0, max:1)
230
200
  if args[0]
231
201
  options[:options]['name'] = args[0]
232
202
  end
@@ -248,9 +218,7 @@ class Morpheus::Cli::BlueprintsCommand
248
218
  end
249
219
  params = Morpheus::Cli::OptionTypes.prompt(add_blueprint_option_types, options[:options], @api_client, options[:params])
250
220
  params.deep_compact!
251
- #blueprint_payload = params.select {|k,v| ['name', 'description', 'category'].include?(k) }
252
221
  # expects no namespace, just the config
253
- #payload = blueprint_payload
254
222
  payload.deep_merge!(params)
255
223
  end
256
224
  @blueprints_interface.setopts(options)
@@ -260,12 +228,8 @@ class Morpheus::Cli::BlueprintsCommand
260
228
  end
261
229
 
262
230
  json_response = @blueprints_interface.create(payload)
263
-
264
- if options[:json]
265
- print JSON.pretty_generate(json_response)
266
- print "\n"
267
- elsif !options[:quiet]
268
- blueprint = json_response["blueprint"]
231
+ blueprint = json_response['blueprint']
232
+ render_response(json_response, options, 'blueprint') do
269
233
  print_green_success "Added blueprint #{blueprint['name']}"
270
234
  if !options[:no_prompt]
271
235
  if options[:payload].nil? && ::Morpheus::Cli::OptionTypes::confirm("Would you like to add a tier now?", options.merge({default: false}))
@@ -275,11 +239,11 @@ class Morpheus::Cli::BlueprintsCommand
275
239
  end
276
240
  else
277
241
  # print details
278
- get([blueprint['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
242
+ return _get(blueprint["id"], options)
279
243
  end
280
244
  end
281
245
  end
282
- return 0
246
+ return 0, nil
283
247
  rescue RestClient::Exception => e
284
248
  print_rest_exception(e, options)
285
249
  exit 1
@@ -294,7 +258,7 @@ class Morpheus::Cli::BlueprintsCommand
294
258
  opts.on( '--owner USER', "Owner Username or ID" ) do |val|
295
259
  options[:owner] = val == 'null' ? nil : val
296
260
  end
297
- build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
261
+ build_standard_update_options(opts, options)
298
262
  opts.footer = "Update a blueprint.\n" +
299
263
  "[blueprint] is required. This is the name or id of a blueprint."
300
264
  end
@@ -388,7 +352,7 @@ class Morpheus::Cli::BlueprintsCommand
388
352
  options[:owner] = val == 'null' ? nil : val
389
353
  end
390
354
  build_option_type_options(opts, options, update_blueprint_option_types(false))
391
- build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
355
+ build_standard_update_options(opts, options)
392
356
  opts.footer = "Update a blueprint permissions.\n" +
393
357
  "[blueprint] is required. This is the name or id of a blueprint."
394
358
  end
@@ -586,7 +550,7 @@ class Morpheus::Cli::BlueprintsCommand
586
550
  options = {}
587
551
  optparse = Morpheus::Cli::OptionParser.new do |opts|
588
552
  opts.banner = subcommand_usage("[blueprint]")
589
- build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
553
+ build_standard_remove_options(opts, options)
590
554
  opts.footer = "Delete a blueprint." + "\n" +
591
555
  "[blueprint] is required. This is the name or id of a blueprint."
592
556
  end
@@ -2123,6 +2087,7 @@ class Morpheus::Cli::BlueprintsCommand
2123
2087
  }
2124
2088
 
2125
2089
  print_description_list(description_cols, blueprint)
2090
+ print reset,"\n"
2126
2091
  # print_h2 "Tiers"
2127
2092
  if blueprint["config"] && blueprint["config"]["tiers"] && blueprint["config"]["tiers"].keys.size != 0
2128
2093
  print cyan
@@ -2213,7 +2178,8 @@ class Morpheus::Cli::BlueprintsCommand
2213
2178
  else
2214
2179
  #print white,"\nTemplate is empty, use `blueprints add-tier \"#{blueprint['name']}\"`",reset,"\n"
2215
2180
  end
2216
- print reset,"\n"
2181
+ # print reset,"\n"
2182
+ print reset
2217
2183
  end
2218
2184
 
2219
2185
  # this parses the environments => groups => clouds tree structure
@@ -570,16 +570,16 @@ class Morpheus::Cli::BudgetsCommand
570
570
  {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'displayOrder' => 1},
571
571
  # {'fieldName' => 'enabled', 'fieldLabel' => 'Enabled', 'type' => 'checkbox', 'defaultValue' => true},
572
572
  {'fieldName' => 'scope', 'fieldLabel' => 'Scope', 'code' => 'budget.scope', 'type' => 'select', 'selectOptions' => [{'name'=>'Account','value'=>'account'},{'name'=>'Tenant','value'=>'tenant'},{'name'=>'Cloud','value'=>'cloud'},{'name'=>'Group','value'=>'group'},{'name'=>'User','value'=>'user'}], 'defaultValue' => 'account', 'required' => true, 'displayOrder' => 3},
573
- {'fieldName' => 'tenant', 'fieldLabel' => 'Tenant', 'type' => 'select', 'optionSource' => lambda {
573
+ {'fieldName' => 'tenant', 'fieldLabel' => 'Tenant', 'type' => 'select', 'optionSource' => lambda {|api_client, api_params|
574
574
  @options_interface.options_for_source("tenants", {})['data']
575
575
  }, 'required' => true, 'dependsOnCode' => 'budget.scope:tenant', 'displayOrder' => 4},
576
- {'fieldName' => 'user', 'fieldLabel' => 'User', 'type' => 'select', 'optionSource' => lambda {
576
+ {'fieldName' => 'user', 'fieldLabel' => 'User', 'type' => 'select', 'optionSource' => lambda {|api_client, api_params|
577
577
  @options_interface.options_for_source("users", {})['data']
578
578
  }, 'required' => true, 'dependsOnCode' => 'budget.scope:user', 'displayOrder' => 5},
579
- {'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'select', 'optionSource' => lambda {
579
+ {'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'select', 'optionSource' => lambda {|api_client, api_params|
580
580
  @options_interface.options_for_source("groups", {})['data']
581
581
  }, 'required' => true, 'dependsOnCode' => 'budget.scope:group', 'displayOrder' => 6},
582
- {'fieldName' => 'cloud', 'fieldLabel' => 'Cloud', 'type' => 'select', 'optionSource' => lambda {
582
+ {'fieldName' => 'cloud', 'fieldLabel' => 'Cloud', 'type' => 'select', 'optionSource' => lambda {|api_client, api_params|
583
583
  @options_interface.options_for_source("clouds", {})['data']
584
584
  }, 'required' => true, 'dependsOnCode' => 'budget.scope:cloud', 'displayOrder' => 7},
585
585
  {'fieldName' => 'year', 'fieldLabel' => 'Period', 'type' => 'text', 'required' => true, 'defaultValue' => Time.now.year, 'description' => "The period (year) the budget applies to. Default is the current year.", 'displayOrder' => 8},
@@ -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