morpheus-cli 4.2.20 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +26 -0
  4. data/lib/morpheus/api/billing_interface.rb +34 -0
  5. data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
  6. data/lib/morpheus/api/deploy_interface.rb +1 -1
  7. data/lib/morpheus/api/deployments_interface.rb +20 -1
  8. data/lib/morpheus/api/forgot_password_interface.rb +17 -0
  9. data/lib/morpheus/api/instances_interface.rb +7 -0
  10. data/lib/morpheus/api/rest_interface.rb +0 -6
  11. data/lib/morpheus/api/roles_interface.rb +14 -0
  12. data/lib/morpheus/api/search_interface.rb +13 -0
  13. data/lib/morpheus/api/servers_interface.rb +7 -0
  14. data/lib/morpheus/api/usage_interface.rb +18 -0
  15. data/lib/morpheus/cli.rb +6 -3
  16. data/lib/morpheus/cli/apps.rb +3 -4
  17. data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
  18. data/lib/morpheus/cli/backups_command.rb +3 -0
  19. data/lib/morpheus/cli/budgets_command.rb +4 -4
  20. data/lib/morpheus/cli/catalog_command.rb +507 -0
  21. data/lib/morpheus/cli/cli_command.rb +45 -20
  22. data/lib/morpheus/cli/commands/standard/curl_command.rb +26 -12
  23. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
  24. data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
  25. data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
  26. data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
  27. data/lib/morpheus/cli/containers_command.rb +14 -0
  28. data/lib/morpheus/cli/deploy.rb +199 -90
  29. data/lib/morpheus/cli/deployments.rb +342 -29
  30. data/lib/morpheus/cli/deploys.rb +206 -41
  31. data/lib/morpheus/cli/error_handler.rb +7 -0
  32. data/lib/morpheus/cli/forgot_password.rb +133 -0
  33. data/lib/morpheus/cli/groups.rb +1 -1
  34. data/lib/morpheus/cli/health_command.rb +2 -2
  35. data/lib/morpheus/cli/hosts.rb +181 -26
  36. data/lib/morpheus/cli/instances.rb +102 -33
  37. data/lib/morpheus/cli/invoices_command.rb +33 -16
  38. data/lib/morpheus/cli/jobs_command.rb +28 -6
  39. data/lib/morpheus/cli/library_option_lists_command.rb +14 -6
  40. data/lib/morpheus/cli/logs_command.rb +9 -6
  41. data/lib/morpheus/cli/mixins/accounts_helper.rb +7 -6
  42. data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
  43. data/lib/morpheus/cli/mixins/catalog_helper.rb +66 -0
  44. data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -3
  45. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  46. data/lib/morpheus/cli/mixins/print_helper.rb +46 -21
  47. data/lib/morpheus/cli/mixins/provisioning_helper.rb +24 -4
  48. data/lib/morpheus/cli/network_pools_command.rb +14 -6
  49. data/lib/morpheus/cli/option_types.rb +266 -17
  50. data/lib/morpheus/cli/ping.rb +0 -1
  51. data/lib/morpheus/cli/provisioning_licenses_command.rb +2 -2
  52. data/lib/morpheus/cli/remote.rb +35 -12
  53. data/lib/morpheus/cli/reports_command.rb +99 -30
  54. data/lib/morpheus/cli/roles.rb +305 -3
  55. data/lib/morpheus/cli/search_command.rb +182 -0
  56. data/lib/morpheus/cli/service_plans_command.rb +2 -2
  57. data/lib/morpheus/cli/setup.rb +1 -1
  58. data/lib/morpheus/cli/shell.rb +33 -11
  59. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  60. data/lib/morpheus/cli/tasks.rb +20 -21
  61. data/lib/morpheus/cli/tenants_command.rb +1 -1
  62. data/lib/morpheus/cli/usage_command.rb +203 -0
  63. data/lib/morpheus/cli/user_settings_command.rb +1 -0
  64. data/lib/morpheus/cli/users.rb +12 -1
  65. data/lib/morpheus/cli/version.rb +1 -1
  66. data/lib/morpheus/cli/virtual_images.rb +280 -199
  67. data/lib/morpheus/cli/whoami.rb +6 -6
  68. data/lib/morpheus/cli/workflows.rb +34 -41
  69. data/lib/morpheus/formatters.rb +48 -5
  70. data/lib/morpheus/terminal.rb +6 -2
  71. metadata +13 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53993967f9a5131702de6bd1d35c1f4f476b87cc56bff1a130737aacca8001a9
4
- data.tar.gz: 7a86d5a5a788d57aa6f09d5f567f056d96f530e7d2eb2201b943c9115fa4701b
3
+ metadata.gz: 25ca8fc7cabb51b4042eb212172b60191d6863cf9310ffce4e386d016692f7f1
4
+ data.tar.gz: d1183424bef986bb22123d60b45e97ed077c8ea30c5bfd249f8bf503c0a22d13
5
5
  SHA512:
6
- metadata.gz: 2c2ec20a5cd5f47cb1ca3c05867e7063cf5d78fba739467bbc3a8e6e82da0b4a59993a76668ab2e29b518d3aee85a4af3c6859adb150d8eaa643abbb54472214
7
- data.tar.gz: 651c4897a8a28d8d476f0c80e73137c9530dc1cfabcc2b59898ee32cbccc27c321676d285825e61c1085630c0cb2abf8be72cb1ddfc86b33764472b3486f759c
6
+ metadata.gz: d006acc9447822675c3593de443679ac2eee514bf1ffe041ddfa5ddc44223e8edfbbeee9393056c59fb98c14d4beb240b0ce9725a3bcc56eb5e1095066d15d1c
7
+ data.tar.gz: ca41986fc3561f562b799971514b92dfe08027e06ad5443b0e116713fee9132d62b84905c929209be03a7dc0f3855e97adf7fe053e64832067addca692ef4268
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.20
3
+ RUN gem install morpheus-cli -v 5.0.2
4
4
 
5
5
  ENTRYPOINT ["morpheus"]
@@ -342,10 +342,18 @@ class Morpheus::APIClient
342
342
  Morpheus::AuthInterface.new({url: @base_url, client_id: @client_id}).setopts(@options)
343
343
  end
344
344
 
345
+ def forgot
346
+ Morpheus::ForgotPasswordInterface.new(common_interface_options).setopts(@options)
347
+ end
348
+
345
349
  def whoami
346
350
  Morpheus::WhoamiInterface.new(common_interface_options).setopts(@options)
347
351
  end
348
352
 
353
+ def search
354
+ Morpheus::SearchInterface.new(common_interface_options).setopts(@options)
355
+ end
356
+
349
357
  def user_settings
350
358
  Morpheus::UserSettingsInterface.new(common_interface_options).setopts(@options)
351
359
  end
@@ -764,6 +772,24 @@ class Morpheus::APIClient
764
772
  Morpheus::BackupJobsInterface.new(common_interface_options).setopts(@options)
765
773
  end
766
774
 
775
+ def catalog_item_types
776
+ Morpheus::CatalogItemTypesInterface.new(common_interface_options).setopts(@options)
777
+ end
778
+
779
+ def usage
780
+ Morpheus::UsageInterface.new(common_interface_options).setopts(@options)
781
+ end
782
+
783
+ def billing
784
+ Morpheus::BillingInterface.new(common_interface_options).setopts(@options)
785
+ end
786
+
767
787
  # add new interfaces here
768
788
 
789
+ protected
790
+
791
+ def validate_id!(id)
792
+ raise "#{self.class} passed a blank id!" if id.to_s.strip.empty?
793
+ end
794
+
769
795
  end
@@ -0,0 +1,34 @@
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
+ # this is an alias for /usage
10
+ def list(params={})
11
+ execute(method: :get, url: "#{base_path}", params: params)
12
+ end
13
+
14
+ def list_account(params={})
15
+ execute(method: :get, url: "#{base_path}/account", params: params)
16
+ end
17
+
18
+ def list_zones(params={})
19
+ execute(method: :get, url: "#{base_path}/zones", params: params)
20
+ end
21
+
22
+ def list_instances(params={})
23
+ execute(method: :get, url: "#{base_path}/instances", params: params)
24
+ end
25
+
26
+ def list_servers(params={})
27
+ execute(method: :get, url: "#{base_path}/servers", params: params)
28
+ end
29
+
30
+ def list_discovered_servers(params={})
31
+ execute(method: :get, url: "#{base_path}/discoveredServers", params: params)
32
+ end
33
+
34
+ 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
@@ -18,7 +18,7 @@ class Morpheus::DeployInterface < Morpheus::APIClient
18
18
  execute(method: :get, url: "#{base_path}", params: params)
19
19
  end
20
20
 
21
- def get(instance_id, id, params={})
21
+ def get(id, params={})
22
22
  validate_id!(id)
23
23
  execute(method: :get, url: "#{base_path}/#{id}", params: params)
24
24
  end
@@ -40,7 +40,8 @@ class Morpheus::DeploymentsInterface < Morpheus::RestInterface
40
40
  if destination.empty? || destination == "/" || destination == "." || destination.include?("../")
41
41
  raise "#{self.class}.upload_file() passed a bad destination: '#{destination}'"
42
42
  end
43
- url = "#{@base_url}/#{base_path}/#{deployment_id}/versions/#{id}/files"
43
+ # url = "#{@base_url}/#{base_path}/#{deployment_id}/versions/#{id}/files"
44
+ url = "#{base_path}/#{deployment_id}/versions/#{id}/files"
44
45
  if !destination.to_s.empty?
45
46
  url += "/#{destination}"
46
47
  end
@@ -57,4 +58,22 @@ class Morpheus::DeploymentsInterface < Morpheus::RestInterface
57
58
  execute(method: :post, url: url, headers: headers, payload: payload, params: params, timeout: 172800)
58
59
  end
59
60
 
61
+ # upload a file without multipart
62
+ # local_file is the full absolute local filename
63
+ # destination should be the full remote file path, including the file name.
64
+ def destroy_file(deployment_id, id, destination, params={})
65
+ if destination.empty? || destination == "/" || destination == "." || destination.include?("../")
66
+ raise "#{self.class}.upload_file() passed a bad destination: '#{destination}'"
67
+ end
68
+ # url = "#{@base_url}/#{base_path}/#{deployment_id}/versions/#{id}/files"
69
+ url = "#{base_path}/#{deployment_id}/versions/#{id}/files"
70
+ if !destination.to_s.empty?
71
+ url += "/#{destination}"
72
+ end
73
+ # use URI to escape path
74
+ uri = URI.parse(url)
75
+ url = uri.path
76
+ execute(method: :delete, url: url, params: params)
77
+ end
78
+
60
79
  end
@@ -0,0 +1,17 @@
1
+ require 'morpheus/api/api_client'
2
+ # There is no Authorization required for this API.
3
+ class Morpheus::ForgotPasswordInterface < Morpheus::APIClient
4
+
5
+ def authorization_required?
6
+ false
7
+ end
8
+
9
+ def send_email(payload, params={})
10
+ execute(method: :post, url: "/api/forgot/send-email", params: params, payload: payload.to_json)
11
+ end
12
+
13
+ def reset_password(payload, params={})
14
+ execute(method: :post, url: "/api/forgot/reset-password", params: params, payload: payload.to_json)
15
+ end
16
+
17
+ end
@@ -218,6 +218,13 @@ class Morpheus::InstancesInterface < Morpheus::APIClient
218
218
  execute(opts)
219
219
  end
220
220
 
221
+ def snapshots(instance_id, params={})
222
+ url = "#{@base_url}/api/instances/#{instance_id}/snapshots"
223
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
224
+ opts = {method: :get, url: url, headers: headers}
225
+ execute(opts)
226
+ end
227
+
221
228
  def import_snapshot(id, params={}, payload={})
222
229
  url = "#{@base_url}/api/instances/#{id}/import-snapshot"
223
230
  headers = {:authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
@@ -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)
@@ -0,0 +1,13 @@
1
+ require 'morpheus/api/rest_interface'
2
+
3
+ class Morpheus::SearchInterface < Morpheus::APIClient
4
+
5
+ def base_path
6
+ "/api/search"
7
+ end
8
+
9
+ def list(params={})
10
+ execute(method: :get, url: "#{base_path}", params: params)
11
+ end
12
+
13
+ end
@@ -169,4 +169,11 @@ class Morpheus::ServersInterface < Morpheus::APIClient
169
169
  execute(opts)
170
170
  end
171
171
 
172
+ def snapshots(id, params={})
173
+ url = "#{@base_url}/api/servers/#{id}/snapshots"
174
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
175
+ opts = {method: :get, url: url, headers: headers}
176
+ execute(opts)
177
+ end
178
+
172
179
  end
@@ -0,0 +1,18 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::UsageInterface < Morpheus::APIClient
4
+
5
+ def base_path
6
+ "/api/usage" # not /usages ?
7
+ end
8
+
9
+ def list(params={})
10
+ execute(method: :get, url: "#{base_path}", params: params)
11
+ end
12
+
13
+ def get(id, params={})
14
+ validate_id!(id)
15
+ execute(method: :get, url: "#{base_path}/#{id}", params: params)
16
+ end
17
+
18
+ end
@@ -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'
@@ -76,9 +74,11 @@ module Morpheus
76
74
  load 'morpheus/cli/setup.rb'
77
75
  load 'morpheus/cli/login.rb'
78
76
  load 'morpheus/cli/logout.rb'
77
+ load 'morpheus/cli/forgot_password.rb'
79
78
  load 'morpheus/cli/whoami.rb'
80
79
  load 'morpheus/cli/access_token_command.rb'
81
80
  load 'morpheus/cli/user_settings_command.rb'
81
+ load 'morpheus/cli/search_command.rb'
82
82
  load 'morpheus/cli/dashboard_command.rb'
83
83
  load 'morpheus/cli/recent_activity_command.rb' # deprecated, removing soon
84
84
  load 'morpheus/cli/activity_command.rb'
@@ -96,11 +96,12 @@ module Morpheus
96
96
  load 'morpheus/cli/tasks.rb'
97
97
  load 'morpheus/cli/workflows.rb'
98
98
  load 'morpheus/cli/deployments.rb'
99
+ load 'morpheus/cli/deploy.rb'
100
+ load 'morpheus/cli/deploys.rb'
99
101
  load 'morpheus/cli/instances.rb'
100
102
  load 'morpheus/cli/containers_command.rb'
101
103
  load 'morpheus/cli/apps.rb'
102
104
  load 'morpheus/cli/blueprints_command.rb'
103
- load 'morpheus/cli/deploys.rb'
104
105
  load 'morpheus/cli/license.rb'
105
106
  load 'morpheus/cli/instance_types.rb'
106
107
  load 'morpheus/cli/jobs_command.rb'
@@ -174,6 +175,8 @@ module Morpheus
174
175
  load 'morpheus/cli/projects_command.rb'
175
176
  load 'morpheus/cli/backups_command.rb'
176
177
  load 'morpheus/cli/backup_jobs_command.rb'
178
+ load 'morpheus/cli/catalog_command.rb'
179
+ load 'morpheus/cli/usage_command.rb'
177
180
  # add new commands here...
178
181
 
179
182
  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']}"
@@ -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