morpheus-cli 5.3.0.3 → 5.3.1

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/README.md +1 -3
  4. data/lib/morpheus/api/api_client.rb +48 -14
  5. data/lib/morpheus/api/certificate_types_interface.rb +14 -0
  6. data/lib/morpheus/api/certificates_interface.rb +9 -0
  7. data/lib/morpheus/api/integration_types_interface.rb +14 -0
  8. data/lib/morpheus/api/integrations_interface.rb +7 -22
  9. data/lib/morpheus/api/network_services_interface.rb +14 -0
  10. data/lib/morpheus/api/read_interface.rb +23 -0
  11. data/lib/morpheus/api/rest_interface.rb +12 -10
  12. data/lib/morpheus/api/roles_interface.rb +7 -0
  13. data/lib/morpheus/api/servers_interface.rb +7 -0
  14. data/lib/morpheus/api/user_settings_interface.rb +38 -18
  15. data/lib/morpheus/api/vdi_allocations_interface.rb +9 -0
  16. data/lib/morpheus/api/vdi_apps_interface.rb +9 -0
  17. data/lib/morpheus/api/vdi_gateways_interface.rb +9 -0
  18. data/lib/morpheus/api/vdi_interface.rb +28 -0
  19. data/lib/morpheus/api/vdi_pools_interface.rb +19 -0
  20. data/lib/morpheus/cli.rb +9 -2
  21. data/lib/morpheus/cli/apps.rb +59 -75
  22. data/lib/morpheus/cli/catalog_item_types_command.rb +13 -13
  23. data/lib/morpheus/cli/certificates_command.rb +575 -0
  24. data/lib/morpheus/cli/cli_command.rb +61 -6
  25. data/lib/morpheus/cli/clouds.rb +1 -0
  26. data/lib/morpheus/cli/clusters.rb +1 -1
  27. data/lib/morpheus/cli/commands/standard/man_command.rb +4 -5
  28. data/lib/morpheus/cli/hosts.rb +245 -224
  29. data/lib/morpheus/cli/instances.rb +150 -167
  30. data/lib/morpheus/cli/integrations_command.rb +588 -41
  31. data/lib/morpheus/cli/login.rb +7 -0
  32. data/lib/morpheus/cli/mixins/print_helper.rb +33 -18
  33. data/lib/morpheus/cli/mixins/provisioning_helper.rb +3 -3
  34. data/lib/morpheus/cli/mixins/vdi_helper.rb +246 -0
  35. data/lib/morpheus/cli/network_routers_command.rb +22 -9
  36. data/lib/morpheus/cli/networks_command.rb +2 -2
  37. data/lib/morpheus/cli/option_types.rb +34 -33
  38. data/lib/morpheus/cli/remote.rb +1 -1
  39. data/lib/morpheus/cli/reports_command.rb +4 -1
  40. data/lib/morpheus/cli/roles.rb +215 -55
  41. data/lib/morpheus/cli/subnets_command.rb +11 -2
  42. data/lib/morpheus/cli/user_settings_command.rb +268 -57
  43. data/lib/morpheus/cli/vdi_allocations_command.rb +159 -0
  44. data/lib/morpheus/cli/vdi_apps_command.rb +317 -0
  45. data/lib/morpheus/cli/vdi_command.rb +359 -0
  46. data/lib/morpheus/cli/vdi_gateways_command.rb +290 -0
  47. data/lib/morpheus/cli/vdi_pools_command.rb +571 -0
  48. data/lib/morpheus/cli/version.rb +1 -1
  49. data/lib/morpheus/rest_client.rb +30 -0
  50. data/lib/morpheus/terminal.rb +15 -7
  51. metadata +18 -2
@@ -0,0 +1,9 @@
1
+ require 'morpheus/api/rest_interface'
2
+
3
+ class Morpheus::VdiAppsInterface < Morpheus::RestInterface
4
+
5
+ def base_path
6
+ "/api/vdi-apps"
7
+ end
8
+
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'morpheus/api/rest_interface'
2
+
3
+ class Morpheus::VdiGatewaysInterface < Morpheus::RestInterface
4
+
5
+ def base_path
6
+ "/api/vdi-gateways"
7
+ end
8
+
9
+ end
@@ -0,0 +1,28 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ # Interface for VDI Persona that provides viewing and allocating virtual desktops (VDI pools)
4
+ class Morpheus::VdiInterface < Morpheus::APIClient
5
+
6
+ def base_path
7
+ "/api/vdi"
8
+ end
9
+
10
+ # def dashboard(params={})
11
+ # execute(method: :get, url: "#{base_path}/dashboard", params: params)
12
+ # end
13
+
14
+ def list(params={})
15
+ execute(method: :get, url: "#{base_path}", params: params)
16
+ end
17
+
18
+ def get(id, params={})
19
+ validate_id!(id)
20
+ execute(method: :get, url: "#{base_path}/#{id}", params: params)
21
+ end
22
+
23
+ def allocate(id, payload, params={})
24
+ validate_id!(id)
25
+ execute(method: :post, url: "#{base_path}/#{id}/allocate", params: params, payload: payload.to_json)
26
+ end
27
+
28
+ end
@@ -0,0 +1,19 @@
1
+ require 'morpheus/api/rest_interface'
2
+
3
+ class Morpheus::VdiPoolsInterface < Morpheus::RestInterface
4
+
5
+ def base_path
6
+ "/api/vdi-pools"
7
+ end
8
+
9
+ # NOT json, multipart file upload
10
+ def update_logo(id, logo_file)
11
+ url = "#{base_path}/#{id}/update-logo"
12
+ headers = { :params => {}, :authorization => "Bearer #{@access_token}"}
13
+ payload = {}
14
+ payload[:logo] = logo_file
15
+ payload[:multipart] = true
16
+ execute(method: :post, url: url, headers: headers, payload: payload)
17
+ end
18
+
19
+ end
data/lib/morpheus/cli.rb CHANGED
@@ -46,6 +46,7 @@ module Morpheus
46
46
  # load interfaces
47
47
  require 'morpheus/api/api_client.rb'
48
48
  require 'morpheus/api/rest_interface.rb'
49
+ require 'morpheus/api/read_interface.rb'
49
50
  Dir[File.dirname(__FILE__) + "/api/**/*.rb"].each {|file| load file }
50
51
 
51
52
  # load mixins
@@ -175,9 +176,15 @@ module Morpheus
175
176
  load 'morpheus/cli/projects_command.rb'
176
177
  load 'morpheus/cli/backups_command.rb'
177
178
  load 'morpheus/cli/backup_jobs_command.rb'
178
- load 'morpheus/cli/catalog_item_types_command.rb' # catalog-types
179
- load 'morpheus/cli/service_catalog_command.rb' # catalog
179
+ load 'morpheus/cli/catalog_item_types_command.rb' # self-service
180
+ load 'morpheus/cli/service_catalog_command.rb' # catalog (Service Catalog persona)
180
181
  load 'morpheus/cli/usage_command.rb'
182
+ load 'morpheus/cli/vdi_pools_command.rb'
183
+ load 'morpheus/cli/vdi_allocations_command.rb'
184
+ load 'morpheus/cli/vdi_apps_command.rb'
185
+ load 'morpheus/cli/vdi_gateways_command.rb'
186
+ load 'morpheus/cli/vdi_command.rb' # (VDI persona)
187
+ load 'morpheus/cli/certificates_command.rb' # (VDI persona)
181
188
  # add new commands here...
182
189
 
183
190
  end
@@ -88,7 +88,7 @@ class Morpheus::Cli::Apps
88
88
  opts.on('-a', '--details', "Display all details: memory and storage usage used / max values." ) do
89
89
  options[:details] = true
90
90
  end
91
- build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
91
+ build_standard_list_options(opts, options)
92
92
  opts.footer = "List apps."
93
93
  end
94
94
  optparse.parse!(args)
@@ -97,64 +97,53 @@ class Morpheus::Cli::Apps
97
97
  options[:phrase] = args.join(" ")
98
98
  end
99
99
  connect(options)
100
- begin
101
- if options[:type]
102
- params['type'] = [options[:type]].flatten.collect {|it| it.to_s.strip.split(",") }.flatten.collect {|it| it.to_s.strip }
103
- end
104
- if options[:blueprint]
105
- blueprint_ids = [options[:blueprint]].flatten.collect {|it| it.to_s.strip.split(",") }.flatten.collect {|it| it.to_s.strip }
106
- params['blueprintId'] = blueprint_ids.collect do |blueprint_id|
107
- if blueprint_id.to_s =~ /\A\d{1,}\Z/
108
- return blueprint_id
109
- else
110
- blueprint = find_blueprint_by_name_or_id(blueprint_id)
111
- return 1 if blueprint.nil?
112
- blueprint['id']
113
- end
100
+
101
+ if options[:type]
102
+ params['type'] = [options[:type]].flatten.collect {|it| it.to_s.strip.split(",") }.flatten.collect {|it| it.to_s.strip }
103
+ end
104
+ if options[:blueprint]
105
+ blueprint_ids = [options[:blueprint]].flatten.collect {|it| it.to_s.strip.split(",") }.flatten.collect {|it| it.to_s.strip }
106
+ params['blueprintId'] = blueprint_ids.collect do |blueprint_id|
107
+ if blueprint_id.to_s =~ /\A\d{1,}\Z/
108
+ return blueprint_id
109
+ else
110
+ blueprint = find_blueprint_by_name_or_id(blueprint_id)
111
+ return 1 if blueprint.nil?
112
+ blueprint['id']
114
113
  end
115
114
  end
116
- if options[:owner]
117
- owner_ids = [options[:owner]].flatten.collect {|it| it.to_s.strip.split(",") }.flatten.collect {|it| it.to_s.strip }
118
- params['ownerId'] = owner_ids.collect do |owner_id|
119
- if owner_id.to_s =~ /\A\d{1,}\Z/
120
- return owner_id
121
- else
122
- user = find_available_user_option(owner_id)
123
- return 1 if user.nil?
124
- user['id']
125
- end
115
+ end
116
+ if options[:owner]
117
+ owner_ids = [options[:owner]].flatten.collect {|it| it.to_s.strip.split(",") }.flatten.collect {|it| it.to_s.strip }
118
+ params['ownerId'] = owner_ids.collect do |owner_id|
119
+ if owner_id.to_s =~ /\A\d{1,}\Z/
120
+ return owner_id
121
+ else
122
+ user = find_available_user_option(owner_id)
123
+ return 1 if user.nil?
124
+ user['id']
126
125
  end
127
126
  end
128
- params.merge!(parse_list_options(options))
129
- account = nil
130
- if options[:owner]
131
- created_by_ids = find_all_user_ids(account ? account['id'] : nil, options[:owner])
132
- return if created_by_ids.nil?
133
- params['createdBy'] = created_by_ids
134
- # params['ownerId'] = created_by_ids # 4.2.1+
135
- end
136
-
137
- params['showDeleted'] = options[:showDeleted] if options.key?(:showDeleted)
138
- params['deleted'] = options[:deleted] if options.key?(:deleted)
127
+ end
128
+ params.merge!(parse_list_options(options))
129
+ account = nil
130
+ if options[:owner]
131
+ created_by_ids = find_all_user_ids(account ? account['id'] : nil, options[:owner])
132
+ return if created_by_ids.nil?
133
+ params['createdBy'] = created_by_ids
134
+ # params['ownerId'] = created_by_ids # 4.2.1+
135
+ end
139
136
 
140
- @apps_interface.setopts(options)
141
- if options[:dry_run]
142
- print_dry_run @apps_interface.dry.list(params)
143
- return
144
- end
137
+ params['showDeleted'] = options[:showDeleted] if options.key?(:showDeleted)
138
+ params['deleted'] = options[:deleted] if options.key?(:deleted)
145
139
 
146
- json_response = @apps_interface.list(params)
147
- if options[:json]
148
- puts as_json(json_response, options, "apps")
149
- return 0
150
- elsif options[:yaml]
151
- puts as_yaml(json_response, options, "apps")
152
- return 0
153
- elsif options[:csv]
154
- puts records_as_csv(json_response['apps'], options)
155
- return 0
156
- end
157
-
140
+ @apps_interface.setopts(options)
141
+ if options[:dry_run]
142
+ print_dry_run @apps_interface.dry.list(params)
143
+ return
144
+ end
145
+ json_response = @apps_interface.list(params)
146
+ render_response(json_response, options, "apps") do
158
147
  apps = json_response['apps']
159
148
  title = "Morpheus Apps"
160
149
  subtitles = []
@@ -167,10 +156,8 @@ class Morpheus::Cli::Apps
167
156
  print_results_pagination(json_response)
168
157
  end
169
158
  print reset,"\n"
170
- rescue RestClient::Exception => e
171
- print_rest_exception(e, options)
172
- exit 1
173
159
  end
160
+ return 0, nil
174
161
  end
175
162
 
176
163
  def count(args)
@@ -645,7 +632,7 @@ class Morpheus::Cli::Apps
645
632
  opts.on('--refresh-until STATUS', String, "Refresh until a specified status is reached.") do |val|
646
633
  options[:refresh_until_status] = val.to_s.downcase
647
634
  end
648
- build_common_options(opts, options, [:json, :yaml, :csv, :fields, :outfile, :dry_run, :remote])
635
+ build_standard_get_options(opts, options)
649
636
  opts.footer = "Get details about an app.\n" +
650
637
  "[app] is required. This is the name or id of an app. Supports 1-N [app] arguments."
651
638
  end
@@ -665,19 +652,19 @@ class Morpheus::Cli::Apps
665
652
 
666
653
  end
667
654
 
668
- def _get(arg, options={})
669
- begin
670
- app = find_app_by_name_or_id(arg)
671
- @apps_interface.setopts(options)
672
- if options[:dry_run]
673
- print_dry_run @apps_interface.dry.get(app['id'])
674
- return
675
- end
676
- json_response = @apps_interface.get(app['id'])
677
-
678
- render_result = render_with_format(json_response, options, 'blueprint')
679
- return 0 if render_result
680
-
655
+ def _get(id, options={})
656
+ app = nil
657
+ if id.to_s !~ /\A\d{1,}\Z/
658
+ app = find_app_by_name_or_id(id)
659
+ id = app['id']
660
+ end
661
+ @apps_interface.setopts(options)
662
+ if options[:dry_run]
663
+ print_dry_run @apps_interface.dry.get(id)
664
+ return
665
+ end
666
+ json_response = @apps_interface.get(id.to_i)
667
+ render_response(json_response, options, 'app') do
681
668
  app = json_response['app']
682
669
  app_tiers = app['appTiers']
683
670
  print_h1 "App Details", [], options
@@ -799,14 +786,11 @@ class Morpheus::Cli::Apps
799
786
  print cyan, "Refreshing in #{options[:refresh_interval] > 1 ? options[:refresh_interval].to_i : options[:refresh_interval]} seconds"
800
787
  sleep_with_dots(options[:refresh_interval])
801
788
  print "\n"
802
- _get(arg, options)
789
+ _get(app['id'], options)
803
790
  end
804
791
  end
805
-
806
- rescue RestClient::Exception => e
807
- print_rest_exception(e, options)
808
- exit 1
809
792
  end
793
+ return 0, nil
810
794
  end
811
795
 
812
796
  def update(args)
@@ -532,22 +532,22 @@ EOT
532
532
 
533
533
  def add_catalog_item_type_option_types
534
534
  [
535
- {'code' => 'catalogItemType.type', 'shorthand' => '-t', 'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => [{'name' => 'Instance', 'value' => 'instance'}, {'name' => 'Blueprint', 'value' => 'blueprint'}, {'name' => 'Workflow', 'value' => 'workflow'}], 'defaultValue' => 'instance', 'required' => true},
536
- {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
537
- {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text'},
538
- {'fieldName' => 'enabled', 'fieldLabel' => 'Enabled', 'type' => 'checkbox', 'defaultValue' => true},
539
- {'fieldName' => 'featured', 'fieldLabel' => 'Featured', 'type' => 'checkbox', 'defaultValue' => false},
540
- {'fieldName' => 'visibility', 'fieldLabel' => 'Visibility', 'type' => 'select', 'selectOptions' => [{'name' => 'Private', 'value' => 'private'}, {'name' => 'Public', 'value' => 'public'}], 'defaultValue' => 'private', 'required' => true},
541
- {'fieldName' => 'iconPath', 'fieldLabel' => 'Logo', 'type' => 'select', 'optionSource' => 'iconList'},
542
- #{'fieldName' => 'optionTypes', 'fieldLabel' => 'Option Types', 'type' => 'text', 'description' => 'Option Types to include, comma separated list of names or IDs.'},
543
- {'dependsOnCode' => 'catalogItemType.type:instance', 'fieldName' => 'config', 'fieldLabel' => 'Config', 'type' => 'code-editor', 'description' => 'JSON or YAML', 'required' => true},
544
- {'dependsOnCode' => 'catalogItemType.type:blueprint', 'fieldName' => 'blueprint', 'fieldLabel' => 'Blueprint', 'type' => 'select', 'optionSource' => 'blueprints', 'description' => 'Choose a blueprint to apply to the catalog item.', 'required' => true, 'noParams' => true},
545
- {'dependsOnCode' => 'catalogItemType.type:blueprint', 'fieldName' => 'appSpec', 'fieldLabel' => 'App Spec', 'type' => 'code-editor', 'description' => 'Enter a spec in the for the App, the Scribe YAML format', 'required' => true},
546
- {'dependsOnCode' => 'catalogItemType.type:workflow', 'fieldName' => 'workflow', 'fieldLabel' => 'Workflow', 'type' => 'select', 'optionSource' => 'operationWorkflows', 'description' => 'Enter a spec in the for the App, the Scribe YAML format', 'noParams' => true},
535
+ {'code' => 'catalogItemType.type', 'shorthand' => '-t', 'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => [{'name' => 'Instance', 'value' => 'instance'}, {'name' => 'Blueprint', 'value' => 'blueprint'}, {'name' => 'Workflow', 'value' => 'workflow'}], 'defaultValue' => 'instance', 'required' => true, 'displayOrder' => 1},
536
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 2},
537
+ {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'displayOrder' => 3},
538
+ {'fieldName' => 'enabled', 'fieldLabel' => 'Enabled', 'type' => 'checkbox', 'defaultValue' => true, 'displayOrder' => 4},
539
+ {'fieldName' => 'featured', 'fieldLabel' => 'Featured', 'type' => 'checkbox', 'defaultValue' => false, 'displayOrder' => 5},
540
+ {'fieldName' => 'visibility', 'fieldLabel' => 'Visibility', 'type' => 'select', 'selectOptions' => [{'name' => 'Private', 'value' => 'private'}, {'name' => 'Public', 'value' => 'public'}], 'defaultValue' => 'private', 'required' => true, 'displayOrder' => 6},
541
+ {'fieldName' => 'iconPath', 'fieldLabel' => 'Logo', 'type' => 'select', 'optionSource' => 'iconList', 'displayOrder' => 7},
542
+ #{'fieldName' => 'optionTypes', 'fieldLabel' => 'Option Types', 'type' => 'text', 'description' => 'Option Types to include, comma separated list of names or IDs.', 'displayOrder' => 8},
543
+ {'dependsOnCode' => 'catalogItemType.type:instance', 'fieldName' => 'config', 'fieldLabel' => 'Config', 'type' => 'code-editor', 'description' => 'JSON or YAML', 'required' => true, 'displayOrder' => 9},
544
+ {'dependsOnCode' => 'catalogItemType.type:blueprint', 'fieldName' => 'blueprint', 'fieldLabel' => 'Blueprint', 'type' => 'select', 'optionSource' => 'blueprints', 'description' => 'Choose a blueprint to apply to the catalog item.', 'required' => true, 'noParams' => true, 'displayOrder' => 10},
545
+ {'dependsOnCode' => 'catalogItemType.type:blueprint', 'fieldName' => 'appSpec', 'fieldLabel' => 'App Spec', 'type' => 'code-editor', 'description' => 'Enter a spec in the for the App, the Scribe YAML format', 'required' => true, 'displayOrder' => 11},
546
+ {'dependsOnCode' => 'catalogItemType.type:workflow', 'fieldName' => 'workflow', 'fieldLabel' => 'Workflow', 'type' => 'select', 'optionSource' => 'operationWorkflows', 'description' => 'Enter a spec in the for the App, the Scribe YAML format', 'noParams' => true, 'displayOrder' => 12},
547
547
  {'dependsOnCode' => 'catalogItemType.type:workflow', 'fieldName' => 'context', 'fieldLabel' => 'Context Type', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
548
548
  [{'name' => "Select", 'value' => ""}, {'name' => "None", 'value' => "appliance"}, {'name' => "Instance", 'value' => "instance"}, {'name' => "Server", 'value' => "server"}]
549
549
  }, 'description' => 'Context for operational workflow, determines target type', 'defaultValue' => 'Select', 'required' => false},
550
- {'fieldName' => 'content', 'fieldLabel' => 'Content', 'type' => 'code-editor', 'description' => 'Wiki Page Content describing the catalog item'}
550
+ {'fieldName' => 'content', 'fieldLabel' => 'Content', 'type' => 'code-editor', 'description' => 'Wiki Page Content describing the catalog item', 'displayOrder' => 13}
551
551
  ]
552
552
  end
553
553
 
@@ -0,0 +1,575 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::CertificatesCommand
4
+ include Morpheus::Cli::CliCommand
5
+
6
+ set_command_name :'certificates'
7
+ set_command_description "Certificates: View and manage SSL certificates."
8
+
9
+ register_subcommands :list, :get, :add, :update, :remove
10
+ register_subcommands :list_types, :get_type
11
+
12
+ def connect(opts)
13
+ @api_client = establish_remote_appliance_connection(opts)
14
+ @certificates_interface = @api_client.certificates
15
+ @certificate_types_interface = @api_client.certificate_types
16
+ end
17
+
18
+ def handle(args)
19
+ handle_subcommand(args)
20
+ end
21
+
22
+ def list(args)
23
+ options = {}
24
+ params = {}
25
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
26
+ opts.banner = subcommand_usage("[search]")
27
+ build_standard_list_options(opts, options)
28
+ opts.footer = "List certificates."
29
+ end
30
+ optparse.parse!(args)
31
+ if args.count > 0
32
+ options[:phrase] = args.join(" ")
33
+ end
34
+ connect(options)
35
+ params.merge!(parse_list_options(options))
36
+ @certificates_interface.setopts(options)
37
+ if options[:dry_run]
38
+ print_dry_run @certificates_interface.dry.list(params)
39
+ return
40
+ end
41
+ json_response = @certificates_interface.list(params)
42
+ render_response(json_response, options, certificate_list_key) do
43
+ certificates = json_response[certificate_list_key]
44
+ print_h1 "Morpheus Certificates", parse_list_subtitles(options), options
45
+ if certificates.empty?
46
+ print cyan,"No certificates found.",reset,"\n"
47
+ else
48
+ list_columns = {
49
+ "ID" => 'id',
50
+ "Name" => 'name',
51
+ "Issued To" => lambda {|it| it['commonName'] },
52
+ "Cert Type" => lambda {|it| it['certType'] },
53
+ "Domain Name" => lambda {|it| it['domainName'] },
54
+ }.upcase_keys!
55
+ print as_pretty_table(certificates, list_columns, options)
56
+ print_results_pagination(json_response)
57
+ end
58
+ print reset,"\n"
59
+ end
60
+ return 0, nil
61
+ end
62
+
63
+ def get(args)
64
+ params = {}
65
+ options = {}
66
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
67
+ opts.banner = subcommand_usage("[certificate]")
68
+ build_standard_get_options(opts, options)
69
+ opts.footer = <<-EOT
70
+ Get details about a specific certificate.
71
+ [certificate] is required. This is the name or id of a certificate.
72
+ EOT
73
+ end
74
+ optparse.parse!(args)
75
+ verify_args!(args:args, optparse:optparse, min:1)
76
+ connect(options)
77
+ params.merge!(parse_query_options(options))
78
+ id_list = parse_id_list(args)
79
+ return run_command_for_each_arg(id_list) do |arg|
80
+ _get(arg, params, options)
81
+ end
82
+ end
83
+
84
+ def _get(id, params, options)
85
+ certificate = nil
86
+ if id.to_s !~ /\A\d{1,}\Z/
87
+ certificate = find_certificate_by_name_or_id(id)
88
+ return 1, "certificate not found for #{id}" if certificate.nil?
89
+ id = certificate['id']
90
+ end
91
+ @certificates_interface.setopts(options)
92
+ if options[:dry_run]
93
+ print_dry_run @certificates_interface.dry.get(id, params)
94
+ return
95
+ end
96
+ json_response = @certificates_interface.get(id, params)
97
+ certificate = json_response[certificate_object_key]
98
+ render_response(json_response, options, certificate_object_key) do
99
+ print_h1 "Certificate Details", [], options
100
+ print cyan
101
+ show_columns = {
102
+ "ID" => 'id',
103
+ "Name" => 'name',
104
+ "Description" => 'description',
105
+ "Issued To" => lambda {|it| it['commonName'] },
106
+ "Cert Type" => lambda {|it| it['certType'] },
107
+ "Domain Name" => lambda {|it| it['domainName'] },
108
+ "Wildcard" => lambda {|it| format_boolean(it['wildcard']) },
109
+ }
110
+ print_description_list(show_columns, certificate, options)
111
+ print reset,"\n"
112
+ end
113
+ return 0, nil
114
+ end
115
+
116
+ def add(args)
117
+ options = {}
118
+ params = {}
119
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
120
+ opts.banner = subcommand_usage("[name] -t CODE [options]")
121
+ # opts.on('-t', '--type CODE', "Certificate Type code, see `#{command_name} list-types` for available type codes") do |val|
122
+ # options[:options]['type'] = val
123
+ # end
124
+ build_option_type_options(opts, options, add_certificate_option_types)
125
+ build_option_type_options(opts, options, add_certificate_advanced_option_types)
126
+ build_standard_add_options(opts, options)
127
+ opts.footer = <<-EOT
128
+ Create a new certificate.
129
+ [name] is required. This is the name of the new certificate
130
+ Configuration options vary by certificate type.
131
+ EOT
132
+ end
133
+ optparse.parse!(args)
134
+ verify_args!(args:args, optparse:optparse, min:0, max:1)
135
+ options[:options]['name'] = args[0] if args[0]
136
+ connect(options)
137
+ payload = {}
138
+ if options[:payload]
139
+ payload = options[:payload]
140
+ payload.deep_merge!({certificate_object_key => parse_passed_options(options)})
141
+ else
142
+ payload.deep_merge!({certificate_object_key => parse_passed_options(options)})
143
+ # Type prompt first
144
+ #params['type'] = Morpheus::Cli::OptionTypes.no_prompt([{'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => [{'name' => 'Instance', 'value' => 'instance'}, {'name' => 'Blueprint', 'value' => 'blueprint'}, {'name' => 'Workflow', 'value' => 'workflow'}], 'defaultValue' => 'instance', 'required' => true}], options[:options], @api_client, options[:params])['type']
145
+ v_prompt = Morpheus::Cli::OptionTypes.prompt(add_certificate_option_types(), options[:options], @api_client, options[:params])
146
+ params.deep_merge!(v_prompt)
147
+ advanced_config = Morpheus::Cli::OptionTypes.no_prompt(add_certificate_advanced_option_types, options[:options], @api_client, options[:params])
148
+ advanced_config.deep_compact!
149
+ params.deep_merge!(advanced_config)
150
+
151
+ # lookup type by name or code to validate it exists and to prompt for its optionTypes
152
+ # set certificate.type=code because the api expects it that way.
153
+ if params['type'].to_s.empty?
154
+ raise_command_error "missing required option: --type TYPE", args, optparse
155
+ end
156
+ certificate_type = find_certificate_type_by_name_or_code_id(params['type'])
157
+ if certificate_type.nil?
158
+ print_red_alert "certificate type not found for #{params['type']}"
159
+ return 1, "certificate type not found for #{params['type']}"
160
+ end
161
+ params['type'] = certificate_type['code']
162
+ config_option_types = certificate_type['optionTypes']
163
+ if config_option_types.nil?
164
+ config_option_types = @certificate_types_interface.option_types(certificate_type['id'])['optionTypes']
165
+ end
166
+ if config_option_types.nil?
167
+ print yellow,"No option types found for certificate type: #{certificate_type['name']} (#{certificate_type['code']})", reset, "\n"
168
+ end
169
+ if config_option_types && config_option_types.size > 0
170
+ # optionTypes do not need fieldContext: 'certificate'
171
+ config_option_types.each do |opt|
172
+ if opt['fieldContext'] == 'certificate' || opt['fieldContext'] == 'domain'
173
+ opt['fieldContext'] = nil
174
+ end
175
+ end
176
+ # reject hardcoded optionTypes
177
+ config_option_types = config_option_types.reject {|it| it['fieldName'] == 'name' || it['fieldName'] == 'description' || it['fieldName'] == 'domainName' }
178
+ config_prompt = Morpheus::Cli::OptionTypes.prompt(config_option_types, options[:options], @api_client, options[:params])
179
+ config_prompt.deep_compact!
180
+ params.deep_merge!(config_prompt)
181
+ end
182
+ # convert checkbox "on" and "off" to true and false
183
+ params.booleanize!
184
+ payload[certificate_object_key].deep_merge!(params)
185
+ end
186
+ @certificates_interface.setopts(options)
187
+ if options[:dry_run]
188
+ print_dry_run @certificates_interface.dry.create(payload)
189
+ return 0, nil
190
+ end
191
+ json_response = @certificates_interface.create(payload)
192
+ certificate = json_response[certificate_object_key]
193
+ render_response(json_response, options, certificate_object_key) do
194
+ print_green_success "Added certificate #{certificate['name']}"
195
+ return _get(certificate["id"], {}, options)
196
+ end
197
+ return 0, nil
198
+ end
199
+
200
+ def update(args)
201
+ options = {}
202
+ params = {}
203
+ payload = {}
204
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
205
+ opts.banner = subcommand_usage("[certificate] [options]")
206
+ build_option_type_options(opts, options, update_certificate_option_types)
207
+ build_option_type_options(opts, options, update_certificate_advanced_option_types)
208
+ opts.on(nil, '--no-refresh', "Skip refresh on update.") do
209
+ payload['refresh'] = 'false'
210
+ end
211
+ build_standard_update_options(opts, options)
212
+ opts.footer = <<-EOT
213
+ Update a certificate.
214
+ [certificate] is required. This is the name or id of a certificate.
215
+ EOT
216
+ end
217
+ optparse.parse!(args)
218
+ verify_args!(args:args, optparse:optparse, count:1)
219
+ connect(options)
220
+ certificate = find_certificate_by_name_or_id(args[0])
221
+ return 1 if certificate.nil?
222
+ payload = {}
223
+ if options[:payload]
224
+ payload = options[:payload]
225
+ payload.deep_merge!({certificate_object_key => parse_passed_options(options)})
226
+ else
227
+ payload.deep_merge!({certificate_object_key => parse_passed_options(options)})
228
+ # do not prompt on update
229
+ v_prompt = Morpheus::Cli::OptionTypes.no_prompt(update_certificate_option_types, options[:options], @api_client, options[:params])
230
+ v_prompt.deep_compact!
231
+ params.deep_merge!(v_prompt)
232
+ advanced_config = Morpheus::Cli::OptionTypes.no_prompt(update_certificate_advanced_option_types, options[:options], @api_client, options[:params])
233
+ advanced_config.deep_compact!
234
+ params.deep_merge!(advanced_config)
235
+ # convert checkbox "on" and "off" to true and false
236
+ params.booleanize!
237
+ payload.deep_merge!({certificate_object_key => params})
238
+ if payload[certificate_object_key].empty? # || options[:no_prompt]
239
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
240
+ end
241
+ end
242
+ @certificates_interface.setopts(options)
243
+ if options[:dry_run]
244
+ print_dry_run @certificates_interface.dry.update(certificate['id'], payload)
245
+ return
246
+ end
247
+ json_response = @certificates_interface.update(certificate['id'], payload)
248
+ certificate = json_response[certificate_object_key]
249
+ render_response(json_response, options, certificate_object_key) do
250
+ print_green_success "Updated certificate #{certificate['name']}"
251
+ return _get(certificate["id"], {}, options)
252
+ end
253
+ return 0, nil
254
+ end
255
+
256
+ def remove(args)
257
+ options = {}
258
+ params = {}
259
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
260
+ opts.banner = subcommand_usage("[certificate] [options]")
261
+ build_standard_remove_options(opts, options)
262
+ opts.footer = <<-EOT
263
+ Delete a certificate.
264
+ [certificate] is required. This is the name or id of a certificate.
265
+ EOT
266
+ end
267
+ optparse.parse!(args)
268
+ verify_args!(args:args, optparse:optparse, count:1)
269
+ connect(options)
270
+ certificate = find_certificate_by_name_or_id(args[0])
271
+ return 1 if certificate.nil?
272
+ @certificates_interface.setopts(options)
273
+ if options[:dry_run]
274
+ print_dry_run @certificates_interface.dry.destroy(certificate['id'], params)
275
+ return
276
+ end
277
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the certificate #{certificate['name']}?")
278
+ return 9, "aborted command"
279
+ end
280
+ json_response = @certificates_interface.destroy(certificate['id'], params)
281
+ render_response(json_response, options) do
282
+ print_green_success "Removed certificate #{certificate['name']}"
283
+ end
284
+ return 0, nil
285
+ end
286
+
287
+
288
+ def list_types(args)
289
+ options = {}
290
+ params = {}
291
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
292
+ opts.banner = subcommand_usage("[search]")
293
+ opts.on('--optionTypes [true|false]', String, "Include optionTypes in the response. Default is false.") do |val|
294
+ params['optionTypes'] = (val.to_s == '' || val.to_s == 'on' || val.to_s == 'true')
295
+ end
296
+ build_standard_list_options(opts, options)
297
+ opts.footer = "List certificate types."
298
+ end
299
+ optparse.parse!(args)
300
+ connect(options)
301
+ # verify_args!(args:args, optparse:optparse, count:0)
302
+ if args.count > 0
303
+ options[:phrase] = args.join(" ")
304
+ end
305
+ params.merge!(parse_list_options(options))
306
+ @certificate_types_interface.setopts(options)
307
+ if options[:dry_run]
308
+ print_dry_run @certificate_types_interface.dry.list(params)
309
+ return
310
+ end
311
+ json_response = @certificate_types_interface.list(params)
312
+ render_response(json_response, options, certificate_type_list_key) do
313
+ certificate_types = json_response[certificate_type_list_key]
314
+ print_h1 "Morpheus Certificate Types", parse_list_subtitles(options), options
315
+ if certificate_types.empty?
316
+ print cyan,"No certificate types found.",reset,"\n"
317
+ else
318
+ list_columns = certificate_type_column_definitions.upcase_keys!
319
+ print as_pretty_table(certificate_types, list_columns, options)
320
+ print_results_pagination(json_response)
321
+ end
322
+ print reset,"\n"
323
+ end
324
+ return 0, nil
325
+ end
326
+
327
+ def get_type(args)
328
+ params = {'optionTypes' => true}
329
+ options = {}
330
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
331
+ opts.banner = subcommand_usage("[type]")
332
+ opts.on('--optionTypes [true|false]', String, "Include optionTypes in the response. Default is true.") do |val|
333
+ params['optionTypes'] = (val.to_s == '' || val.to_s == 'on' || val.to_s == 'true')
334
+ end
335
+ build_standard_get_options(opts, options)
336
+ opts.footer = <<-EOT
337
+ Get details about a specific certificate type.
338
+ [type] is required. This is the name or id of a certificate type.
339
+ EOT
340
+ end
341
+ optparse.parse!(args)
342
+ verify_args!(args:args, optparse:optparse, min:1)
343
+ connect(options)
344
+ params.merge!(parse_query_options(options))
345
+ id_list = parse_id_list(args)
346
+ return run_command_for_each_arg(id_list) do |arg|
347
+ _get_type(arg, params, options)
348
+ end
349
+ end
350
+
351
+ def _get_type(id, params, options)
352
+ certificate_type = nil
353
+ if id.to_s !~ /\A\d{1,}\Z/
354
+ certificate_type = find_certificate_type_by_name_or_code(id)
355
+ return 1, "certificate type not found for name or code '#{id}'" if certificate_type.nil?
356
+ id = certificate_type['id']
357
+ end
358
+ # /api/certificate-types does not return optionTypes by default, use ?optionTypes=true
359
+ @certificate_types_interface.setopts(options)
360
+ if options[:dry_run]
361
+ print_dry_run @certificate_types_interface.dry.get(id, params)
362
+ return
363
+ end
364
+ json_response = @certificate_types_interface.get(id, params)
365
+ certificate_type = json_response[certificate_type_object_key]
366
+ render_response(json_response, options, certificate_type_object_key) do
367
+ print_h1 "Certificate Type Details", [], options
368
+ print cyan
369
+ show_columns = certificate_type_column_definitions
370
+ print_description_list(show_columns, certificate_type)
371
+
372
+ if certificate_type['optionTypes'] && certificate_type['optionTypes'].size > 0
373
+ print_h2 "Option Types"
374
+ opt_columns = [
375
+ # {"ID" => lambda {|it| it['id'] } },
376
+ {"FIELD NAME" => lambda {|it| (it['fieldContext'] && it['fieldContext'] != 'certificate') ? [it['fieldContext'], it['fieldName']].join('.') : it['fieldName'] } },
377
+ {"FIELD LABEL" => lambda {|it| it['fieldLabel'] } },
378
+ {"TYPE" => lambda {|it| it['type'] } },
379
+ {"DEFAULT" => lambda {|it| it['defaultValue'] } },
380
+ {"REQUIRED" => lambda {|it| format_boolean it['required'] } },
381
+ # {"DESCRIPTION" => lambda {|it| it['description'] }, # do it!
382
+ ]
383
+ print as_pretty_table(certificate_type['optionTypes'], opt_columns)
384
+ else
385
+ # print cyan,"No option types found for this certificate type.","\n",reset
386
+ end
387
+
388
+ print reset,"\n"
389
+ end
390
+ return 0, nil
391
+ end
392
+
393
+ private
394
+
395
+ def format_certificate_type(certificate)
396
+ (certificate['certificateType']['name'] || certificate['certificateType']['code']) rescue certificate['certificateType'].to_s
397
+ end
398
+
399
+ def add_certificate_option_types
400
+ [
401
+ {'code' => 'certificate.type', 'shorthand' => '-t', 'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'optionSource' => lambda { |api_client, api_params|
402
+ # @certificate_types_interface.list(max:-1)[certificate_list_key].collect {|it|
403
+ get_available_certificate_types().collect {|it|
404
+ {'name' => it['code'], 'value' => it['id']}
405
+ } }, 'required' => true, 'description' => "Certificate Type code, see `#{command_name} list-types` for available type codes", 'defaultValue' => "internal"},
406
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Name of the certificate'},
407
+ {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'required' => false, 'description' => 'Description of the certificate'},
408
+ {'fieldName' => 'domainName', 'fieldLabel' => 'Domain Name', 'type' => 'text', 'required' => false, 'description' => 'Domain Name of the certificate'},
409
+ # {'fieldName' => 'enabled', 'fieldLabel' => 'Enabled', 'type' => 'checkbox', 'defaultValue' => true, 'description' => 'Can be used to disable a certificate'}
410
+ ]
411
+ end
412
+
413
+ def add_certificate_advanced_option_types
414
+ []
415
+ end
416
+
417
+ def update_certificate_option_types
418
+ list = add_certificate_option_types.collect {|it|
419
+ it.delete('required')
420
+ it.delete('defaultValue')
421
+ it
422
+ }
423
+ list = list.reject {|it| ["type"].include? it['fieldName'] }
424
+ list
425
+ end
426
+
427
+ def update_certificate_advanced_option_types
428
+ add_certificate_advanced_option_types.collect {|it|
429
+ it.delete('required')
430
+ it.delete('defaultValue')
431
+ it
432
+ }
433
+ end
434
+
435
+ def certificate_object_key
436
+ 'certificate'
437
+ end
438
+
439
+ def certificate_list_key
440
+ 'certificates'
441
+ end
442
+
443
+ def find_certificate_by_name_or_id(val)
444
+ if val.to_s =~ /\A\d{1,}\Z/
445
+ return find_certificate_by_id(val)
446
+ else
447
+ return find_certificate_by_name(val)
448
+ end
449
+ end
450
+
451
+ def find_certificate_by_id(id)
452
+ begin
453
+ json_response = @certificates_interface.get(id.to_i)
454
+ return json_response[certificate_object_key]
455
+ rescue RestClient::Exception => e
456
+ if e.response && e.response.code == 404
457
+ print_red_alert "certificate not found by id '#{id}'"
458
+ else
459
+ raise e
460
+ end
461
+ end
462
+ end
463
+
464
+ def find_certificate_by_name(name)
465
+ json_response = @certificates_interface.list({name: name.to_s})
466
+ certificates = json_response[certificate_list_key]
467
+ if certificates.empty?
468
+ print_red_alert "certificate not found by name '#{name}'"
469
+ return nil
470
+ elsif certificates.size > 1
471
+ print_red_alert "#{certificates.size} certificates found by name '#{name}'"
472
+ puts_error as_pretty_table(certificates, [:id, :name], {color:red})
473
+ print_red_alert "Try using ID instead"
474
+ print reset,"\n"
475
+ return nil
476
+ else
477
+ return certificates[0]
478
+ end
479
+ end
480
+
481
+ def format_certificate_status(certificate, return_color=cyan)
482
+ out = ""
483
+ status_string = certificate['status']
484
+ if status_string.nil? || status_string.empty? || status_string == "unknown"
485
+ out << "#{white}UNKNOWN#{certificate['statusMessage'] ? "#{return_color} - #{certificate['statusMessage']}" : ''}#{return_color}"
486
+ # elsif certificate['enabled'] == false
487
+ # out << "#{red}DISABLED#{certificate['statusMessage'] ? "#{return_color} - #{certificate['statusMessage']}" : ''}#{return_color}"
488
+ elsif status_string == 'ok'
489
+ out << "#{green}#{status_string.upcase}#{return_color}"
490
+ elsif status_string == 'error' || status_string == 'offline'
491
+ out << "#{red}#{status_string ? status_string.upcase : 'N/A'}#{certificate['statusMessage'] ? "#{return_color} - #{certificate['statusMessage']}" : ''}#{return_color}"
492
+ else
493
+ out << "#{yellow}#{status_string.upcase}#{return_color}"
494
+ end
495
+ out
496
+ end
497
+
498
+
499
+ def certificate_type_column_definitions()
500
+ {
501
+ "ID" => 'id',
502
+ "Code" => 'code',
503
+ "Name" => 'name',
504
+ # "Description" => 'description',
505
+ # "Enabled" => lambda {|it| format_boolean(it['enabled']) },
506
+ "Creatable" => lambda {|it| format_boolean(it['creatable']) },
507
+ }
508
+ end
509
+
510
+ def certificate_type_object_key
511
+ 'certificateType'
512
+ end
513
+
514
+ def certificate_type_list_key
515
+ 'certificateTypes'
516
+ end
517
+
518
+ def find_certificate_type_by_name_or_code_id(val, params={})
519
+ if val.to_s =~ /\A\d{1,}\Z/
520
+ return find_certificate_type_by_id(val, params)
521
+ else
522
+ return find_certificate_type_by_name_or_code(val)
523
+ end
524
+ end
525
+
526
+ def find_certificate_type_by_id(id, params={})
527
+ begin
528
+ json_response = @certificate_types_interface.get(id.to_i, params)
529
+ return json_response[certificate_type_object_key]
530
+ rescue RestClient::Exception => e
531
+ if e.response && e.response.code == 404
532
+ print_red_alert "certificate not found by id '#{id}'"
533
+ else
534
+ raise e
535
+ end
536
+ end
537
+ end
538
+
539
+ def find_certificate_type_by_name(name, params={})
540
+ json_response = @certificate_types_interface.list(params.merge({name: name.to_s}))
541
+ certificate_types = json_response[certificate_type_list_key]
542
+ if certificate_types.empty?
543
+ print_red_alert "certificate type not found by name '#{name}'"
544
+ return nil
545
+ elsif certificate_types.size > 1
546
+ print_red_alert "#{certificate_types.size} certificate types found by name '#{name}'"
547
+ puts_error as_pretty_table(certificate_types, [:id, :code, :name], {color:red})
548
+ print_red_alert "Try using ID instead"
549
+ print reset,"\n"
550
+ return nil
551
+ else
552
+ return certificate_types[0]
553
+ end
554
+ end
555
+
556
+ def get_available_certificate_types(refresh=false)
557
+ if !@available_certificate_types || refresh
558
+ @available_certificate_types = @certificate_types_interface.list(max:10000)[certificate_type_list_key]
559
+ end
560
+ return @available_certificate_types
561
+ end
562
+
563
+ def find_certificate_type_by_name_or_code(name)
564
+ records = get_available_certificate_types()
565
+ record = records.find { |z| z['name'].downcase == name.downcase || z['code'].downcase == name.downcase}
566
+ record = record ? record : records.find { |z| z['id'].to_s == name.to_s }
567
+ if record
568
+ return record
569
+ else
570
+ print_red_alert "certificate type not found by '#{name}'"
571
+ return nil
572
+ end
573
+ end
574
+
575
+ end