morpheus-cli 4.0.0.1 → 4.1.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/morpheus/api.rb +10 -0
  3. data/lib/morpheus/api/api_client.rb +24 -3
  4. data/lib/morpheus/api/clouds_interface.rb +15 -0
  5. data/lib/morpheus/api/clusters_interface.rb +276 -0
  6. data/lib/morpheus/api/library_compute_type_layouts_interface.rb +26 -0
  7. data/lib/morpheus/api/logs_interface.rb +6 -0
  8. data/lib/morpheus/api/network_subnet_types_interface.rb +26 -0
  9. data/lib/morpheus/api/network_subnets_interface.rb +47 -0
  10. data/lib/morpheus/api/provision_types_interface.rb +6 -0
  11. data/lib/morpheus/api/security_groups_interface.rb +12 -3
  12. data/lib/morpheus/api/servers_interface.rb +15 -0
  13. data/lib/morpheus/api/service_plans_interface.rb +30 -0
  14. data/lib/morpheus/api/subnets_interface.rb +47 -0
  15. data/lib/morpheus/cli.rb +1 -0
  16. data/lib/morpheus/cli/apps.rb +20 -18
  17. data/lib/morpheus/cli/cli_command.rb +5 -1
  18. data/lib/morpheus/cli/clusters.rb +3952 -0
  19. data/lib/morpheus/cli/containers_command.rb +70 -2
  20. data/lib/morpheus/cli/hosts.rb +69 -53
  21. data/lib/morpheus/cli/instances.rb +33 -33
  22. data/lib/morpheus/cli/library_container_types_command.rb +2 -1
  23. data/lib/morpheus/cli/library_option_lists_command.rb +13 -8
  24. data/lib/morpheus/cli/mixins/accounts_helper.rb +43 -0
  25. data/lib/morpheus/cli/mixins/print_helper.rb +10 -2
  26. data/lib/morpheus/cli/mixins/provisioning_helper.rb +53 -3
  27. data/lib/morpheus/cli/networks_command.rb +883 -36
  28. data/lib/morpheus/cli/option_types.rb +37 -14
  29. data/lib/morpheus/cli/roles.rb +78 -77
  30. data/lib/morpheus/cli/user_settings_command.rb +34 -5
  31. data/lib/morpheus/cli/version.rb +1 -1
  32. metadata +10 -2
@@ -0,0 +1,47 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::NetworkSubnetsInterface < Morpheus::APIClient
4
+ def initialize(access_token, refresh_token,expires_at = nil, base_url=nil)
5
+ @access_token = access_token
6
+ @refresh_token = refresh_token
7
+ @base_url = base_url
8
+ @expires_at = expires_at
9
+ end
10
+
11
+ def get(network_id, id, params={})
12
+ raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
13
+ url = "#{@base_url}/api/networks/#{network_id}/subnets/#{id}"
14
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
15
+ opts = {method: :get, url: url, headers: headers}
16
+ execute(opts)
17
+ end
18
+
19
+ def list(network_id, params={})
20
+ url = "#{@base_url}/api/networks/#{network_id}/subnets"
21
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
22
+ opts = {method: :get, url: url, headers: headers}
23
+ execute(opts)
24
+ end
25
+
26
+ def create(network_id, payload)
27
+ url = "#{@base_url}/api/networks/#{network_id}/subnets"
28
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
29
+ opts = {method: :post, url: url, headers: headers, payload: payload.to_json}
30
+ execute(opts)
31
+ end
32
+
33
+ def update(network_id, id, payload)
34
+ url = "#{@base_url}/api/networks/#{network_id}/subnets/#{id}"
35
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
36
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
37
+ execute(opts)
38
+ end
39
+
40
+ def destroy(network_id, id, params={})
41
+ url = "#{@base_url}/api/networks/#{network_id}/subnets/#{id}"
42
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
43
+ opts = {method: :delete, url: url, headers: headers}
44
+ execute(opts)
45
+ end
46
+
47
+ end
@@ -8,6 +8,12 @@ class Morpheus::ProvisionTypesInterface < Morpheus::APIClient
8
8
  @expires_at = expires_at
9
9
  end
10
10
 
11
+ def list(params={})
12
+ url = "#{@base_url}/api/provision-types"
13
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
14
+ execute(method: :get, url: url, headers: headers)
15
+ end
16
+
11
17
  def get(options=nil)
12
18
  url = "#{@base_url}/api/provision-types"
13
19
  headers = { params: {}, authorization: "Bearer #{@access_token}" }
@@ -15,9 +15,18 @@ class Morpheus::SecurityGroupsInterface < Morpheus::APIClient
15
15
  end
16
16
 
17
17
  def get(id, params={})
18
- url = "#{@base_url}/api/security-groups/#{id}"
19
- headers = { params: params, authorization: "Bearer #{@access_token}" }
20
- execute(method: :get, url: url, headers: headers)
18
+ url = "#{@base_url}/api/security-groups"
19
+ headers = { params: {}, authorization: "Bearer #{@access_token}" }
20
+
21
+ if options.is_a?(Hash)
22
+ headers[:params].merge!(options)
23
+ elsif options.is_a?(Numeric)
24
+ url = "#{url}/#{options}"
25
+ elsif options.is_a?(String)
26
+ headers[:params]['name'] = options
27
+ end
28
+ opts = {method: :get, url: url, headers: headers}
29
+ execute(opts)
21
30
  end
22
31
 
23
32
  def create(payload)
@@ -122,6 +122,21 @@ class Morpheus::ServersInterface < Morpheus::APIClient
122
122
  execute(opts)
123
123
  end
124
124
 
125
+ def service_plan(options)
126
+ url = "#{@base_url}/api/service-plans"
127
+ headers = { params: {}, authorization: "Bearer #{@access_token}" }
128
+
129
+ if options.is_a?(Hash)
130
+ headers[:params].merge!(options)
131
+ elsif options.is_a?(Numeric)
132
+ url = "#{url}/#{options}"
133
+ elsif options.is_a?(String)
134
+ headers[:params]['name'] = options
135
+ end
136
+ opts = {method: :get, url: url, headers: headers}
137
+ execute(opts)
138
+ end
139
+
125
140
  def volumes(id)
126
141
  url = "#{@base_url}/api/servers/#{id}/volumes"
127
142
  headers = { :params => {},:authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
@@ -0,0 +1,30 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::ServicePlansInterface < Morpheus::APIClient
4
+ def initialize(access_token, refresh_token,expires_at = nil, base_url=nil)
5
+ @access_token = access_token
6
+ @refresh_token = refresh_token
7
+ @base_url = base_url
8
+ @expires_at = expires_at
9
+ end
10
+
11
+ def list(params={})
12
+ url = "#{@base_url}/api/service-plans"
13
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
14
+ execute(method: :get, url: url, headers: headers)
15
+ end
16
+
17
+ def get(options=nil)
18
+ url = "#{@base_url}/api/service-plans"
19
+ headers = { params: {}, authorization: "Bearer #{@access_token}" }
20
+
21
+ if options.is_a?(Hash)
22
+ headers[:params].merge!(options)
23
+ elsif options.is_a?(Numeric)
24
+ url = "#{@base_url}/api/service-plans/#{options}"
25
+ elsif options.is_a?(String)
26
+ headers[:params]['name'] = options
27
+ end
28
+ execute(method: :get, url: url, headers: headers)
29
+ end
30
+ end
@@ -0,0 +1,47 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::SubnetsInterface < Morpheus::APIClient
4
+ def initialize(access_token, refresh_token,expires_at = nil, base_url=nil)
5
+ @access_token = access_token
6
+ @refresh_token = refresh_token
7
+ @base_url = base_url
8
+ @expires_at = expires_at
9
+ end
10
+
11
+ def get(id, params={})
12
+ raise "#{self.class}.get() passed a blank id!" if id.to_s == ''
13
+ url = "#{@base_url}/api/subnets/#{id}"
14
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
15
+ opts = {method: :get, url: url, headers: headers}
16
+ execute(opts)
17
+ end
18
+
19
+ def list(params={})
20
+ url = "#{@base_url}/api/subnets"
21
+ headers = { params: params, authorization: "Bearer #{@access_token}" }
22
+ opts = {method: :get, url: url, headers: headers}
23
+ execute(opts)
24
+ end
25
+
26
+ def create(network_id, payload)
27
+ url = "#{@base_url}/api/subnets"
28
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
29
+ opts = {method: :post, url: url, headers: headers, payload: payload.to_json}
30
+ execute(opts)
31
+ end
32
+
33
+ def update(id, payload)
34
+ url = "#{@base_url}/api/subnets/#{id}"
35
+ headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
36
+ opts = {method: :put, url: url, headers: headers, payload: payload.to_json}
37
+ execute(opts)
38
+ end
39
+
40
+ def destroy(id, params={})
41
+ url = "#{@base_url}/api/subnets/#{id}"
42
+ headers = { :params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
43
+ opts = {method: :delete, url: url, headers: headers}
44
+ execute(opts)
45
+ end
46
+
47
+ end
@@ -99,6 +99,7 @@ module Morpheus
99
99
  load 'morpheus/cli/instance_types.rb'
100
100
  load 'morpheus/cli/security_groups.rb'
101
101
  load 'morpheus/cli/security_group_rules.rb'
102
+ load 'morpheus/cli/clusters.rb'
102
103
  load 'morpheus/cli/tenants_command.rb'
103
104
  load 'morpheus/cli/accounts.rb'
104
105
  load 'morpheus/cli/account_groups_command.rb'
@@ -985,7 +985,7 @@ class Morpheus::Cli::Apps
985
985
  options = {}
986
986
  optparse = Morpheus::Cli::OptionParser.new do |opts|
987
987
  opts.banner = subcommand_usage("[app]")
988
- build_common_options(opts, options, [:list, :json, :dry_run, :remote])
988
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
989
989
  opts.footer = "List logs for an app.\n" +
990
990
  "[app] is required. This is the name or id of an app."
991
991
  end
@@ -1005,27 +1005,28 @@ class Morpheus::Cli::Apps
1005
1005
  end
1006
1006
  end
1007
1007
  params = {}
1008
- [:phrase, :offset, :max, :sort, :direction].each do |k|
1009
- params[k] = options[k] unless options[k].nil?
1010
- end
1008
+ params.merge!(parse_list_options(options))
1011
1009
  params[:query] = params.delete(:phrase) unless params[:phrase].nil?
1012
- @apps_interface.setopts(options)
1010
+ @logs_interface.setopts(options)
1013
1011
  if options[:dry_run]
1014
1012
  print_dry_run @logs_interface.dry.container_logs(containers, params)
1015
1013
  return
1016
1014
  end
1017
- logs = @logs_interface.container_logs(containers, params)
1018
- if options[:json]
1019
- puts as_json(logs, options)
1020
- return 0
1015
+ json_response = @logs_interface.container_logs(containers, params)
1016
+ render_result = render_with_format(json_response, options, 'data')
1017
+ return 0 if render_result
1018
+
1019
+ logs = json_response
1020
+ title = "App Logs: #{app['name']}"
1021
+ subtitles = parse_list_subtitles(options)
1022
+ if params[:query]
1023
+ subtitles << "Search: #{params[:query]}".strip
1024
+ end
1025
+ # todo: startMs, endMs, sorts insteaad of sort..etc
1026
+ print_h1 title, subtitles, options
1027
+ if logs['data'].empty?
1028
+ puts "#{cyan}No logs found.#{reset}"
1021
1029
  else
1022
- title = "App Logs: #{app['name']}"
1023
- subtitles = []
1024
- if params[:query]
1025
- subtitles << "Search: #{params[:query]}".strip
1026
- end
1027
- # todo: startMs, endMs, sorts insteaad of sort..etc
1028
- print_h1 title, subtitles, options
1029
1030
  logs['data'].reverse.each do |log_entry|
1030
1031
  log_level = ''
1031
1032
  case log_entry['level']
@@ -1042,9 +1043,10 @@ class Morpheus::Cli::Apps
1042
1043
  end
1043
1044
  puts "[#{log_entry['ts']}] #{log_level} - #{log_entry['message'].to_s.strip}"
1044
1045
  end
1045
- print reset,"\n"
1046
- return 0
1046
+ print_results_pagination({'meta'=>{'total'=>json_response['total'],'size'=>json_response['data'].size,'max'=>(json_response['max'] || options[:max]),'offset'=>(json_response['offset'] || options[:offset] || 0)}})
1047
1047
  end
1048
+ print reset,"\n"
1049
+ return 0
1048
1050
  rescue RestClient::Exception => e
1049
1051
  print_rest_exception(e, options)
1050
1052
  exit 1
@@ -937,7 +937,11 @@ module Morpheus
937
937
  output = as_yaml(json_response, options, object_key)
938
938
  elsif options[:csv]
939
939
  row = object_key ? json_response[object_key] : json_response
940
- output = records_as_csv([row], options)
940
+ if row.is_a?(Array)
941
+ output = records_as_csv(row, options)
942
+ else
943
+ output = records_as_csv([row], options)
944
+ end
941
945
  end
942
946
  if output
943
947
  if options[:outfile]
@@ -0,0 +1,3952 @@
1
+ # require 'yaml'
2
+ require 'io/console'
3
+ require 'rest_client'
4
+ require 'optparse'
5
+ require 'filesize'
6
+ require 'morpheus/cli/cli_command'
7
+ #require 'morpheus/cli/mixins/infrastructure_helper'
8
+
9
+ class Morpheus::Cli::Clusters
10
+ include Morpheus::Cli::CliCommand
11
+ include Morpheus::Cli::ProvisioningHelper
12
+ include Morpheus::Cli::ProcessesHelper
13
+ include Morpheus::Cli::WhoamiHelper
14
+ include Morpheus::Cli::AccountsHelper
15
+
16
+ register_subcommands :list, :count, :get, :view, :add, :update, :remove, :logs, :history, {:'history-details' => :history_details}, {:'history-event' => :history_event_details}
17
+ register_subcommands :list_workers, :add_worker
18
+ register_subcommands :list_masters
19
+ register_subcommands :list_volumes, :remove_volume
20
+ register_subcommands :list_namespaces, :get_namespace, :add_namespace, :update_namespace, :remove_namespace
21
+ register_subcommands :list_containers, :remove_container, :restart_container
22
+ register_subcommands :list_deployments, :remove_deployment, :restart_deployment
23
+ register_subcommands :list_stateful_sets, :remove_stateful_set, :restart_stateful_set
24
+ register_subcommands :list_pods, :remove_pod, :restart_pod
25
+ register_subcommands :list_jobs, :remove_job
26
+ register_subcommands :list_services, :remove_service
27
+ register_subcommands :update_permissions
28
+ register_subcommands :api_config, :view_api_token, :view_kube_config
29
+ register_subcommands :wiki, :update_wiki
30
+
31
+ def connect(opts)
32
+ @api_client = establish_remote_appliance_connection(opts)
33
+ @clusters_interface = @api_client.clusters
34
+ @groups_interface = @api_client.groups
35
+ @compute_type_layouts_interface = @api_client.library_compute_type_layouts
36
+ @security_groups_interface = @api_client.security_groups
37
+ #@security_group_rules_interface = @api_client.security_group_rules
38
+ @cloud_resource_pools_interface = @api_client.cloud_resource_pools
39
+ @clouds_interface = @api_client.clouds
40
+ @servers_interface = @api_client.servers
41
+ @server_types_interface = @api_client.server_types
42
+ @options_interface = @api_client.options
43
+ @active_group_id = Morpheus::Cli::Groups.active_group
44
+ @provision_types_interface = @api_client.provision_types
45
+ @service_plans_interface = @api_client.service_plans
46
+ @user_groups_interface = @api_client.user_groups
47
+ @accounts_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).accounts
48
+ @logs_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).logs
49
+ #@active_security_group = ::Morpheus::Cli::SecurityGroups.load_security_group_file
50
+ end
51
+
52
+ def handle(args)
53
+ handle_subcommand(args)
54
+ end
55
+
56
+ def list(args)
57
+ options = {}
58
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
59
+ opts.banner = subcommand_usage()
60
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
61
+ opts.footer = "List clusters."
62
+ end
63
+ optparse.parse!(args)
64
+ if args.count != 0
65
+ raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
66
+ end
67
+ connect(options)
68
+ begin
69
+ params = {}
70
+ params.merge!(parse_list_options(options))
71
+ @clusters_interface.setopts(options)
72
+ if options[:dry_run]
73
+ print_dry_run @clusters_interface.dry.list(params)
74
+ return
75
+ end
76
+ json_response = @clusters_interface.list(params)
77
+
78
+ render_result = render_with_format(json_response, options, 'clusters')
79
+ return 0 if render_result
80
+
81
+ title = "Morpheus Clusters"
82
+ subtitles = []
83
+ subtitles += parse_list_subtitles(options)
84
+ print_h1 title, subtitles
85
+
86
+ clusters = json_response['clusters']
87
+
88
+ if clusters.empty?
89
+ print yellow,"No clusters found.",reset,"\n"
90
+ else
91
+ print_clusters_table(clusters, options)
92
+ end
93
+ print reset,"\n"
94
+ return 0
95
+ rescue RestClient::Exception => e
96
+ print_rest_exception(e, options)
97
+ exit 1
98
+ end
99
+ end
100
+
101
+ def count(args)
102
+ options = {}
103
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
104
+ opts.banner = subcommand_usage("[options]")
105
+ build_common_options(opts, options, [:query, :remote, :dry_run])
106
+ opts.footer = "Get the number of clusters."
107
+ end
108
+ optparse.parse!(args)
109
+ if args.count != 0
110
+ raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args}\n#{optparse}"
111
+ end
112
+ connect(options)
113
+ begin
114
+ params = {}
115
+ params.merge!(parse_list_options(options))
116
+ @clusters_interface.setopts(options)
117
+ if options[:dry_run]
118
+ print_dry_run @clusters_interface.dry.get(params)
119
+ return
120
+ end
121
+ json_response = @clusters_interface.get(params)
122
+ # print number only
123
+ if json_response['meta'] && json_response['meta']['total']
124
+ print cyan, json_response['meta']['total'], reset, "\n"
125
+ else
126
+ print yellow, "unknown", reset, "\n"
127
+ end
128
+ rescue RestClient::Exception => e
129
+ print_rest_exception(e, options)
130
+ exit 1
131
+ end
132
+ end
133
+
134
+ def get(args)
135
+ options = {}
136
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
137
+ opts.banner = subcommand_usage("[id]")
138
+ opts.on( nil, '--hosts', "Display masters and workers" ) do
139
+ options[:show_masters] = true
140
+ options[:show_workers] = true
141
+ end
142
+ opts.on( nil, '--masters', "Display masters" ) do
143
+ options[:show_masters] = true
144
+ end
145
+ opts.on( nil, '--workers', "Display workers" ) do
146
+ options[:show_workers] = true
147
+ end
148
+ opts.on( nil, '--permissions', "Display permissions" ) do
149
+ options[:show_perms] = true
150
+ end
151
+ opts.on('--refresh [SECONDS]', String, "Refresh until status is provisioned,failed. Default interval is #{default_refresh_interval} seconds.") do |val|
152
+ options[:refresh_until_status] ||= "provisioned,failed"
153
+ if !val.to_s.empty?
154
+ options[:refresh_interval] = val.to_f
155
+ end
156
+ end
157
+ opts.on('--refresh-until STATUS', String, "Refresh until a specified status is reached.") do |val|
158
+ options[:refresh_until_status] = val.to_s.downcase
159
+ end
160
+ build_common_options(opts, options, [:json, :dry_run, :remote])
161
+ opts.footer = "Get details about a cluster."
162
+ end
163
+ optparse.parse!(args)
164
+ if args.count < 1
165
+ raise_command_error "wrong number of arguments, expected 1-N and got (#{args.count}) #{args}\n#{optparse}"
166
+ end
167
+ connect(options)
168
+ id_list = parse_id_list(args)
169
+ return run_command_for_each_arg(id_list) do |arg|
170
+ _get(arg, options)
171
+ end
172
+ end
173
+
174
+ def _get(arg, options={})
175
+
176
+ begin
177
+ @clusters_interface.setopts(options)
178
+
179
+ if options[:dry_run]
180
+ if arg.to_s =~ /\A\d{1,}\Z/
181
+ print_dry_run @clusters_interface.dry.get(arg.to_i)
182
+ else
183
+ print_dry_run @clusters_interface.dry.list({name:arg})
184
+ end
185
+ return 0
186
+ end
187
+ cluster = find_cluster_by_name_or_id(arg)
188
+ return 1 if cluster.nil?
189
+ json_response = nil
190
+ if arg.to_s =~ /\A\d{1,}\Z/
191
+ json_response = {'cluster' => cluster} # skip redundant request
192
+ else
193
+ json_response = @clusters_interface.get(cluster['id'])
194
+ end
195
+
196
+ if options[:json]
197
+ print JSON.pretty_generate(json_response)
198
+ print "\n"
199
+ return
200
+ end
201
+
202
+ cluster = json_response['cluster']
203
+ worker_stats = cluster['workerStats']
204
+ clusterType = find_cluster_type_by_id(cluster['type']['id'])
205
+
206
+ print_h1 "Morpheus Cluster"
207
+ print cyan
208
+ description_cols = {
209
+ "ID" => 'id',
210
+ "Name" => 'name',
211
+ "Type" => lambda { |it| it['type']['name'] },
212
+ #"Group" => lambda { |it| it['site']['name'] },
213
+ "Cloud" => lambda { |it| it['zone']['name'] },
214
+ "Location" => lambda { |it| it['location'] },
215
+ "Layout" => lambda { |it| it['layout'] ? it['layout']['name'] : ''},
216
+ "API Url" => 'serviceUrl',
217
+ "Visibility" => lambda { |it| it['visibility'].to_s.capitalize },
218
+ #"Groups" => lambda {|it| it['groups'].collect {|g| g.instance_of?(Hash) ? g['name'] : g.to_s }.join(', ') },
219
+ #"Owner" => lambda {|it| it['owner'].instance_of?(Hash) ? it['owner']['name'] : it['ownerId'] },
220
+ #"Tenant" => lambda {|it| it['account'].instance_of?(Hash) ? it['account']['name'] : it['accountId'] },
221
+ "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
222
+ "Created By" => lambda {|it| it['createdBy'] ? it['createdBy']['username'] : '' },
223
+ "Enabled" => lambda { |it| format_boolean(it['enabled']) },
224
+ "Status" => lambda { |it| format_cluster_status(it) }
225
+ }
226
+ print_description_list(description_cols, cluster)
227
+
228
+ print_h2 "Cluster Details"
229
+ print cyan
230
+
231
+ print "Namespaces: #{cluster['namespacesCount']}".center(20) if !cluster['namespacesCount'].nil?
232
+ print "Masters: #{cluster['mastersCount']}".center(20) if !cluster['mastersCount'].nil?
233
+ print "Workers: #{cluster['workersCount']}".center(20) if !clusterType['workersCount'].nil?
234
+ print "Volumes: #{cluster['volumesCount']}".center(20) if !cluster['volumesCount'].nil?
235
+ print "Containers: #{cluster['containersCount']}".center(20) if !cluster['containersCount'].nil?
236
+ print "Services: #{cluster['servicesCount']}".center(20) if !cluster['servicesCount'].nil?
237
+ print "Jobs: #{cluster['jobsCount']}".center(20) if !cluster['jobsCount'].nil?
238
+ print "Pods: #{cluster['podsCount']}".center(20) if !cluster['podsCount'].nil?
239
+ print "Deployments: #{cluster['deploymentsCount']}".center(20) if !cluster['deploymentsCount'].nil?
240
+ print "\n"
241
+
242
+ if options[:show_masters]
243
+ masters_json = @clusters_interface.list_masters(cluster['id'], options)
244
+ if masters_json.nil? || masters_json['masters'].empty?
245
+ print_h2 "Masters"
246
+ print yellow,"No masters found.",reset,"\n"
247
+ else
248
+ masters = masters_json['masters']
249
+ print_h2 "Masters"
250
+ rows = masters.collect do |server|
251
+ {
252
+ id: server['id'],
253
+ name: server['name'],
254
+ type: (server['type']['name'] rescue server['type']),
255
+ status: format_server_status(server),
256
+ power: format_server_power_state(server, cyan)
257
+ }
258
+ end
259
+ columns = [:id, :name, :status, :power]
260
+ puts as_pretty_table(rows, columns, options)
261
+ end
262
+ end
263
+ if options[:show_workers]
264
+ workers_json = @clusters_interface.list_workers(cluster['id'], options)
265
+ if workers_json.nil? || workers_json['workers'].empty?
266
+ print_h2 "Workers"
267
+ print yellow,"No workers found.",reset,"\n"
268
+ else
269
+ workers = workers_json['workers']
270
+ print_h2 "Workers"
271
+ rows = workers.collect do |server|
272
+ {
273
+ id: server['id'],
274
+ name: server['name'],
275
+ type: (server['type']['name'] rescue server['type']),
276
+ status: format_server_status(server),
277
+ power: format_server_power_state(server, cyan)
278
+ }
279
+ end
280
+ columns = [:id, :name, :status, :power]
281
+ puts as_pretty_table(rows, columns, options)
282
+ end
283
+ end
284
+
285
+ if worker_stats
286
+ print_h2 "Worker Usage"
287
+ print cyan
288
+ # print "CPU Usage: #{worker_stats['cpuUsage']}".center(20)
289
+ # print "Memory: #{worker_stats['usedMemory']}".center(20)
290
+ # print "Storage: #{worker_stats['usedStorage']}".center(20)
291
+ print_stats_usage(worker_stats, {include: [:max_cpu, :avg_cpu, :memory, :storage]})
292
+ print reset,"\n"
293
+ end
294
+
295
+ if options[:show_perms]
296
+ permissions = cluster['permissions']
297
+ print_permissions(permissions)
298
+ end
299
+
300
+ # refresh until a status is reached
301
+ if options[:refresh_until_status]
302
+ if options[:refresh_interval].nil? || options[:refresh_interval].to_f < 0
303
+ options[:refresh_interval] = default_refresh_interval
304
+ end
305
+ statuses = options[:refresh_until_status].to_s.downcase.split(",").collect {|s| s.strip }.select {|s| !s.to_s.empty? }
306
+ if !statuses.include?(cluster['status'])
307
+ print cyan, "Refreshing in #{options[:refresh_interval] > 1 ? options[:refresh_interval].to_i : options[:refresh_interval]} seconds"
308
+ sleep_with_dots(options[:refresh_interval])
309
+ print "\n"
310
+ _get(arg, options)
311
+ end
312
+ end
313
+
314
+ return 0
315
+ rescue RestClient::Exception => e
316
+ print_rest_exception(e, options)
317
+ exit 1
318
+ end
319
+ end
320
+
321
+ def view(args)
322
+ options = {}
323
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
324
+ opts.banner = subcommand_usage("[cluster]")
325
+ opts.on('-w','--wiki', "Open the wiki tab for this cluster") do
326
+ options[:link_tab] = "wiki"
327
+ end
328
+ opts.on('--tab VALUE', String, "Open a specific tab") do |val|
329
+ options[:link_tab] = val.to_s
330
+ end
331
+ build_common_options(opts, options, [:dry_run, :remote])
332
+ opts.footer = "View a cluster in a web browser" + "\n" +
333
+ "[cluster] is required. This is the name or id of a cluster. Supports 1-N [cluster] arguments."
334
+ end
335
+ optparse.parse!(args)
336
+ if args.count < 1
337
+ raise_command_error "wrong number of arguments, expected 1-N and got (#{args.count}) #{args}\n#{optparse}"
338
+ end
339
+ connect(options)
340
+ id_list = parse_id_list(args)
341
+ return run_command_for_each_arg(id_list) do |arg|
342
+ _view(arg, options)
343
+ end
344
+ end
345
+
346
+
347
+ def _view(arg, options={})
348
+ begin
349
+ cluster = find_cluster_by_name_or_id(arg)
350
+ return 1 if cluster.nil?
351
+
352
+ link = "#{@appliance_url}/login/oauth-redirect?access_token=#{@access_token}\\&redirectUri=/infrastructure/clusters/#{cluster['id']}"
353
+ if options[:link_tab]
354
+ link << "#!#{options[:link_tab]}"
355
+ end
356
+
357
+ open_command = nil
358
+ if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
359
+ open_command = "start #{link}"
360
+ elsif RbConfig::CONFIG['host_os'] =~ /darwin/
361
+ open_command = "open #{link}"
362
+ elsif RbConfig::CONFIG['host_os'] =~ /linux|bsd/
363
+ open_command = "xdg-open #{link}"
364
+ end
365
+
366
+ if options[:dry_run]
367
+ puts "system: #{open_command}"
368
+ return 0
369
+ end
370
+
371
+ system(open_command)
372
+
373
+ return 0
374
+ rescue RestClient::Exception => e
375
+ print_rest_exception(e, options)
376
+ exit 1
377
+ end
378
+ end
379
+
380
+ def add(args)
381
+ options = {}
382
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
383
+ opts.banner = subcommand_usage( "[name]")
384
+ opts.on( '--name NAME', "Cluster Name" ) do |val|
385
+ options[:name] = val.to_s
386
+ end
387
+ opts.on("--description [TEXT]", String, "Description") do |val|
388
+ options[:description] = val.to_s
389
+ end
390
+ opts.on( '--resource-name NAME', "Resource Name" ) do |val|
391
+ options[:resourceName] = val.to_s
392
+ end
393
+ opts.on('--tags LIST', String, "Tags") do |val|
394
+ options[:tags] = val
395
+ end
396
+ opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
397
+ options[:group] = val
398
+ end
399
+ opts.on( '-t', '--cluster-type TYPE', "Cluster Type Name or ID" ) do |val|
400
+ options[:clusterType] = val
401
+ end
402
+ opts.on( '-l', '--layout LAYOUT', "Layout Name or ID" ) do |val|
403
+ options[:layout] = val
404
+ end
405
+ opts.on('--visibility [private|public]', String, "Visibility") do |val|
406
+ options[:visibility] = val
407
+ end
408
+ opts.on('--refresh [SECONDS]', String, "Refresh until status is provisioned,failed. Default interval is #{default_refresh_interval} seconds.") do |val|
409
+ options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
410
+ end
411
+ opts.on('--workflow ID', String, "Workflow") do |val|
412
+ params['taskSetId'] = val.to_i
413
+ end
414
+ add_server_options(opts, options)
415
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
416
+ opts.footer = "Create a cluster.\n" +
417
+ "[name] is required. This is the name of the new cluster."
418
+ end
419
+
420
+ optparse.parse!(args)
421
+ if args.count > 1
422
+ raise_command_error "wrong number of arguments, expected 0-2 and got (#{args.count}) #{args}\n#{optparse}"
423
+ end
424
+ connect(options)
425
+
426
+ begin
427
+ payload = nil
428
+ if options[:payload]
429
+ payload = options[:payload]
430
+ # support -O OPTION switch on top of --payload
431
+ payload['cluster'] ||= {}
432
+ if options[:options]
433
+ payload['cluster'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) })
434
+ end
435
+ if args[0]
436
+ payload['cluster']['name'] = args[0]
437
+ elsif options[:name]
438
+ payload['cluster']['name'] = options[:name]
439
+ end
440
+ # if args[1]
441
+ # payload['cluster']['description'] = args[1]
442
+ # elsif options[:description]
443
+ # payload['cluster']['description'] = options[:description]
444
+ # end
445
+ payload['cluster']['server'] ||= {}
446
+ if options[:resourceName]
447
+ payload['cluster']['server']['name'] = options[:resourceName]
448
+ end
449
+ if options[:description]
450
+ payload['cluster']['description'] = options[:description]
451
+ end
452
+ else
453
+ cluster_payload = {}
454
+ server_payload = {"config" => {}}
455
+
456
+ # Cluster Type
457
+ cluster_type_id = nil
458
+ cluster_type = options[:clusterType] ? find_cluster_type_by_name_or_id(options[:clusterType]) : nil
459
+
460
+ if cluster_type
461
+ cluster_type_id = cluster_type['id']
462
+ else
463
+ available_cluster_types = cluster_types_for_dropdown
464
+
465
+ if available_cluster_types.empty?
466
+ print_red_alert "A cluster type is required"
467
+ exit 1
468
+ elsif available_cluster_types.count > 1 && !options[:no_prompt]
469
+ cluster_type_code = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'clusterType', 'type' => 'select', 'fieldLabel' => 'Cluster Type', 'selectOptions' => cluster_types_for_dropdown, 'required' => true, 'description' => 'Select Cluster Type.'}],options[:options],@api_client,{})['clusterType']
470
+ else
471
+ cluster_type_code = available_cluster_types.first['code']
472
+ end
473
+ cluster_type = get_cluster_types.find { |ct| ct['code'] == cluster_type_code }
474
+ end
475
+
476
+ cluster_payload['type'] = cluster_type['code'] # {'id' => cluster_type['id']}
477
+
478
+ # Cluster Name
479
+ if args.empty? && options[:no_prompt]
480
+ print_red_alert "No cluster name provided"
481
+ exit 1
482
+ elsif !args.empty?
483
+ cluster_payload['name'] = args[0]
484
+ elsif options[:name]
485
+ cluster_payload['name'] = options[:name]
486
+ else
487
+ existing_cluster_names = @clusters_interface.list()['clusters'].collect { |cluster| cluster['name'] }
488
+ while cluster_payload['name'].empty?
489
+ name = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => 'Cluster Name', 'required' => true, 'description' => 'Cluster Name.'}],options[:options],@api_client,{})['name']
490
+
491
+ if existing_cluster_names.include?(name)
492
+ print_red_alert "Name must be unique, #{name} already exists"
493
+ else
494
+ cluster_payload['name'] = name
495
+ end
496
+ end
497
+ end
498
+
499
+ # Cluster Description
500
+ # if !args.empty? && args.count > 1
501
+ # cluster_payload['description'] = args[1]
502
+ # elsif options[:description]
503
+ # cluster_payload['description'] = options[:description]
504
+ # elsif !options[:no_prompt]
505
+ # cluster_payload['description'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'type' => 'text', 'fieldLabel' => 'Cluster Description', 'required' => false, 'description' => 'Cluster Description.'}],options[:options],@api_client,{})['description']
506
+ # end
507
+
508
+ # Resource Name
509
+ resourceName = options[:resourceName]
510
+
511
+ if !resourceName
512
+ if options[:no_prompt]
513
+ print_red_alert "No resource name provided"
514
+ exit 1
515
+ else
516
+ resourceName = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'resourceName', 'type' => 'text', 'fieldLabel' => 'Resource Name', 'required' => true, 'description' => 'Resource Name.'}],options[:options],@api_client,{})['resourceName']
517
+ end
518
+ end
519
+
520
+ server_payload['name'] = resourceName
521
+
522
+ # Resource Description
523
+ description = options[:description] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'type' => 'text', 'fieldLabel' => 'Description', 'required' => false, 'description' => 'Resource Description.'}],options[:options],@api_client,{})['description']
524
+ cluster_payload['description'] = description if description
525
+
526
+ tags = options[:tags]
527
+
528
+ if !tags && !options[:no_prompt]
529
+ tags = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'tags', 'type' => 'text', 'fieldLabel' => 'Resource Tags', 'required' => false, 'description' => 'Resource Tags.'}],options[:options],@api_client,{})['tags']
530
+ end
531
+
532
+ server_payload['tags'] = tags if tags
533
+
534
+ # Group / Site
535
+ group = load_group(options)
536
+ cluster_payload['group'] = {'id' => group['id']}
537
+
538
+ # Cloud / Zone
539
+ cloud_id = nil
540
+ cloud = options[:cloud] ? find_cloud_by_name_or_id_for_provisioning(group['id'], options[:cloud]) : nil
541
+ if cloud
542
+ # load full cloud
543
+ cloud = @clouds_interface.get(cloud['id'])['zone']
544
+ cloud_id = cloud['id']
545
+ else
546
+ available_clouds = get_available_clouds(group['id'])
547
+
548
+ if available_clouds.empty?
549
+ print_red_alert "Group #{group['name']} has no available clouds"
550
+ exit 1
551
+ else
552
+ cloud_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'type' => 'select', 'fieldLabel' => 'Cloud', 'selectOptions' => available_clouds, 'required' => true, 'description' => 'Select Cloud.'}],options[:options],@api_client,{groupId: group['id']})['cloud']
553
+ end
554
+ cloud = @clouds_interface.get(cloud_id)['zone']
555
+ end
556
+
557
+ cloud['zoneType'] = get_cloud_type(cloud['zoneType']['id'])
558
+ cluster_payload['cloud'] = {'id' => cloud['id']}
559
+
560
+ # Layout
561
+ layout = options[:layout] ? find_layout_by_name_or_id(options[:layout]) : nil
562
+
563
+ if !layout
564
+ available_layouts = layouts_for_dropdown(cloud['id'], cluster_type['id'])
565
+
566
+ if !available_layouts.empty?
567
+ if available_layouts.count > 1 && !options[:no_prompt]
568
+ layout_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'layout', 'type' => 'select', 'fieldLabel' => 'Layout', 'selectOptions' => available_layouts, 'required' => true, 'description' => 'Select Layout.'}],options[:options],@api_client,{})['layout']
569
+ else
570
+ layout_id = available_layouts.first['id']
571
+ end
572
+ layout = find_layout_by_name_or_id(layout_id)
573
+ end
574
+ end
575
+
576
+ cluster_payload['layout'] = {id: layout['id']}
577
+
578
+ # Plan
579
+ provision_type = (layout && layout['provisionType'] ? layout['provisionType'] : nil) || get_provision_type_for_zone_type(cloud['zoneType']['id'])
580
+ service_plan = prompt_service_plan(cloud['id'], provision_type, options)
581
+
582
+ if service_plan
583
+ server_payload['plan'] = {'id' => service_plan['id'], 'code' => service_plan['code'], 'options' => prompt_service_plan_options(service_plan, options)}
584
+ end
585
+
586
+ # Controller type
587
+ server_types = @server_types_interface.list({max:1, computeTypeId: cluster_type['controllerTypes'].first['id'], zoneTypeId: cloud['zoneType']['id'], useZoneProvisionTypes: true})['serverTypes']
588
+ controller_provision_type = nil
589
+ resource_pool = nil
590
+
591
+ if !server_types.empty?
592
+ controller_type = server_types.first
593
+ controller_provision_type = controller_type['provisionType'] ? (@provision_types_interface.get(controller_type['provisionType']['id'])['provisionType'] rescue nil) : nil
594
+
595
+ if controller_provision_type && resource_pool = prompt_resource_pool(group, cloud, service_plan, controller_provision_type, options)
596
+ server_payload['config']['resourcePool'] = resource_pool['externalId']
597
+ end
598
+ end
599
+
600
+ # Multi-disk / prompt for volumes
601
+ volumes = options[:volumes] || prompt_volumes(service_plan, options.merge({'defaultAddFirstDataVolume': true}), @api_client, {zoneId: cloud['id'], siteId: group['id']})
602
+
603
+ if !volumes.empty?
604
+ server_payload['volumes'] = volumes
605
+ end
606
+
607
+ # Networks
608
+ # NOTE: You must choose subnets in the same availability zone
609
+ if controller_provision_type && controller_provision_type['hasNetworks'] && cloud['zoneType']['code'] != 'esxi'
610
+ server_payload['networkInterfaces'] = options[:networkInterfaces] || prompt_network_interfaces(cloud['id'], provision_type['id'], (resource_pool['id'] rescue nil), options)
611
+ end
612
+
613
+ # Security Groups
614
+ server_payload['securityGroups'] = prompt_security_groups_by_cloud(cloud, provision_type, resource_pool, options)
615
+
616
+ # Visibility
617
+ server_payload['visibility'] = options[:visibility] || (Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'visibility', 'fieldLabel' => 'Visibility', 'type' => 'select', 'defaultValue' => 'private', 'required' => true, 'selectOptions' => [{'name' => 'Private', 'value' => 'private'},{'name' => 'Public', 'value' => 'public'}]}], options[:options], @api_client, {})['visibility'])
618
+
619
+ # Options / Custom Config
620
+ option_type_list = ((controller_type['optionTypes'].reject { |type| !type['enabled'] || type['fieldComponent'] } rescue []) + layout['optionTypes'] +
621
+ (cluster_type['optionTypes'].reject { |type| !type['enabled'] || !type['creatable'] || type['fieldComponent'] } rescue [])).sort { |type| type['displayOrder'] }
622
+
623
+ # remove volume options if volumes were configured
624
+ if !server_payload['volumes'].empty?
625
+ option_type_list = reject_volume_option_types(option_type_list)
626
+ end
627
+ # remove networkId option if networks were configured above
628
+ if !server_payload['networkInterfaces'].empty?
629
+ option_type_list = reject_networking_option_types(option_type_list)
630
+ end
631
+
632
+ server_payload.deep_merge!(Morpheus::Cli::OptionTypes.prompt(option_type_list, options[:options], @api_client, {zoneId: cloud['id'], siteId: group['id'], layoutId: layout['id']}))
633
+
634
+ # Create User
635
+ if !options[:createUser].nil?
636
+ server_payload['config']['createUser'] = options[:createUser]
637
+ elsif !options[:no_prompt]
638
+ current_user['windowsUsername'] || current_user['linuxUsername']
639
+ server_payload['config']['createUser'] = (current_user['windowsUsername'] || current_user['linuxUsername']) && Morpheus::Cli::OptionTypes.confirm("Create Your User?", {:default => true})
640
+ end
641
+
642
+ # User Groups
643
+ userGroup = options[:userGroup] ? find_user_group_by_name_or_id(nil, options[:userGroup]) : nil
644
+
645
+ if userGroup
646
+ server_payload['userGroup'] = userGroup
647
+ elsif !options[:no_prompt]
648
+ userGroupId = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'userGroupId', 'fieldLabel' => 'User Group', 'type' => 'select', 'required' => false, 'optionSource' => 'userGroups'}], options[:options], @api_client, {})['userGroupId']
649
+
650
+ if userGroupId
651
+ server_payload['userGroup'] = {'id' => userGroupId}
652
+ end
653
+ end
654
+
655
+ # Host / Domain
656
+ server_payload['networkDomain'] = options[:domain] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'networkDomain', 'fieldLabel' => 'Network Domain', 'type' => 'select', 'required' => false, 'optionSource' => 'networkDomains'}], options[:options], @api_client, {})['networkDomain']
657
+ server_payload['hostname'] = options[:hostname] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'hostname', 'fieldLabel' => 'Hostname', 'type' => 'text', 'required' => true, 'description' => 'Hostname', 'defaultValue' => resourceName}], options[:options], @api_client)['hostname']
658
+
659
+ # Workflow / Automation
660
+ task_set_id = options[:taskSetId] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'taskSet', 'fieldLabel' => 'Workflow', 'type' => 'select', 'required' => false, 'optionSource' => 'taskSets'}], options[:options], @api_client, {'phase' => 'postProvision'})['taskSet']
661
+
662
+ if !task_set_id.nil?
663
+ server_payload['taskSet'] = {'id' => task_set_id}
664
+ end
665
+
666
+ cluster_payload['server'] = server_payload
667
+ payload = {'cluster' => cluster_payload}
668
+ end
669
+ @clusters_interface.setopts(options)
670
+ if options[:dry_run]
671
+ print_dry_run @clusters_interface.dry.create(payload)
672
+ return
673
+ end
674
+ json_response = @clusters_interface.create(payload)
675
+ if options[:json]
676
+ print JSON.pretty_generate(json_response)
677
+ print "\n"
678
+ elsif json_response['success']
679
+ get_args = [json_response["cluster"]["id"]] + (options[:remote] ? ["-r",options[:remote]] : []) + (options[:refresh_interval] ? ['--refresh', options[:refresh_interval].to_s] : [])
680
+ get(get_args)
681
+ else
682
+ print_rest_errors(json_response, options)
683
+ end
684
+ rescue RestClient::Exception => e
685
+ print_rest_exception(e, options)
686
+ exit 1
687
+ end
688
+ end
689
+
690
+ def update(args)
691
+ options = {}
692
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
693
+ opts.banner = subcommand_usage( "[cluster] --name --description --active")
694
+ opts.on("--name NAME", String, "Updates Cluster Name") do |val|
695
+ options[:name] = val.to_s
696
+ end
697
+ opts.on("--description [TEXT]", String, "Updates Cluster Description") do |val|
698
+ options[:description] = val.to_s
699
+ end
700
+ opts.on("--api-url [TEXT]", String, "Updates Cluster API Url") do |val|
701
+ options[:apiUrl] = val.to_s
702
+ end
703
+ opts.on('--active [on|off]', String, "Can be used to enable / disable the cluster. Default is on") do |val|
704
+ options[:active] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
705
+ end
706
+ opts.on( nil, '--refresh', "Refresh cluster" ) do
707
+ options[:refresh] = true
708
+ end
709
+ opts.on("--tenant ACCOUNT", String, "Account ID or Name" ) do |val|
710
+ options[:tenant] = val
711
+ end
712
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
713
+ opts.footer = "Update a cluster.\n" +
714
+ "[cluster] is required. This is the name or id of an existing cluster."
715
+ end
716
+
717
+ optparse.parse!(args)
718
+ if args.count != 1
719
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
720
+ end
721
+ connect(options)
722
+
723
+ begin
724
+ payload = nil
725
+ cluster = nil
726
+
727
+ if options[:payload]
728
+ payload = options[:payload]
729
+ # support -O OPTION switch on top of --payload
730
+ if options[:options]
731
+ payload['cluster'] ||= {}
732
+ payload['cluster'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) })
733
+ end
734
+
735
+ if !payload['cluster'].empty?
736
+ cluster = find_cluster_by_name_or_id(payload['cluster']['id'] || payload['cluster']['name'])
737
+ end
738
+ else
739
+ cluster = find_cluster_by_name_or_id(args[0])
740
+ cluster_payload = {}
741
+ cluster_payload['name'] = options[:name] if !options[:name].empty?
742
+ cluster_payload['description'] = options[:description] if !options[:description].empty?
743
+ cluster_payload['enabled'] = options[:active] if !options[:active].nil?
744
+ cluster_payload['serviceUrl'] = options[:apiUrl] if !options[:apiUrl].nil?
745
+ cluster_payload['refresh'] = options[:refresh] if options[:refresh] == true
746
+ cluster_payload['tenant'] = options[:tenant] if !options[:tenant].nil?
747
+ payload = {"cluster" => cluster_payload}
748
+ end
749
+
750
+ if !cluster
751
+ print_red_alert "No clusters available for update"
752
+ exit 1
753
+ end
754
+
755
+ has_field_updates = ['name', 'description', 'enabled', 'serviceUrl'].find {|field| payload['cluster'] && !payload['cluster'][field].nil? && payload['cluster'][field] != cluster[field] ? field : nil}
756
+
757
+ if !has_field_updates && cluster_payload['refresh'].nil? && cluster_payload['tenant'].nil?
758
+ print_green_success "Nothing to update"
759
+ exit 1
760
+ end
761
+
762
+ @clusters_interface.setopts(options)
763
+ if options[:dry_run]
764
+ print_dry_run @clusters_interface.dry.update(cluster['id'], payload)
765
+ return
766
+ end
767
+ json_response = @clusters_interface.update(cluster['id'], payload)
768
+ if options[:json]
769
+ print JSON.pretty_generate(json_response)
770
+ print "\n"
771
+ elsif json_response['success']
772
+ get_args = [json_response["cluster"]["id"]] + (options[:remote] ? ["-r",options[:remote]] : []) + (options[:refresh_interval] ? ['--refresh', options[:refresh_interval].to_s] : [])
773
+ get(get_args)
774
+ else
775
+ print_rest_errors(json_response, options)
776
+ end
777
+ end
778
+ end
779
+
780
+ def remove(args)
781
+ options = {}
782
+ query_params = {:removeResources => 'on'}
783
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
784
+ opts.banner = subcommand_usage("[cluster]")
785
+ opts.on('--remove-resources [on|off]', ['on','off'], "Remove Infrastructure. Default is on.") do |val|
786
+ query_params[:removeResources] = val.nil? ? 'on' : val
787
+ end
788
+ opts.on('--preserve-volumes [on|off]', ['on','off'], "Preserve Volumes. Default is off.") do |val|
789
+ query_params[:preserveVolumes] = val.nil? ? 'on' : val
790
+ end
791
+ opts.on('--remove-instances [on|off]', ['on','off'], "Remove Associated Instances. Default is off.") do |val|
792
+ query_params[:removeInstances] = val.nil? ? 'on' : val
793
+ end
794
+ opts.on('--release-eips [on|off]', ['on','off'], "Release EIPs, default is on. Amazon only.") do |val|
795
+ params[:releaseEIPs] = val.nil? ? 'on' : val
796
+ end
797
+ opts.on( '-f', '--force', "Force Delete" ) do
798
+ query_params[:force] = 'on'
799
+ end
800
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
801
+ opts.footer = "Delete a cluster.\n" +
802
+ "[cluster] is required. This is the name or id of an existing cluster."
803
+ end
804
+ optparse.parse!(args)
805
+ if args.count != 1
806
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
807
+ end
808
+ connect(options)
809
+
810
+ begin
811
+ cluster = find_cluster_by_name_or_id(args[0])
812
+
813
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the cluster '#{cluster['name']}'?", options)
814
+ return 9, "aborted command"
815
+ end
816
+ @clusters_interface.setopts(options)
817
+ if options[:dry_run]
818
+ print_dry_run @clusters_interface.dry.destroy(cluster['id'], query_params)
819
+ return
820
+ end
821
+ json_response = @clusters_interface.destroy(cluster['id'], query_params)
822
+ if options[:json]
823
+ print JSON.pretty_generate(json_response)
824
+ print "\n"
825
+ elsif !options[:quiet]
826
+ print_green_success "Cluster #{cluster['name']} is being removed..."
827
+ #list([])
828
+ end
829
+ rescue RestClient::Exception => e
830
+ print_rest_exception(e, options)
831
+ exit 1
832
+ end
833
+ end
834
+
835
+ def logs(args)
836
+ options = {}
837
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
838
+ opts.banner = subcommand_usage("[cluster]")
839
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
840
+ end
841
+ optparse.parse!(args)
842
+ if args.count != 1
843
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
844
+ end
845
+ connect(options)
846
+ begin
847
+ cluster = find_cluster_by_name_or_id(args[0])
848
+ params = {}
849
+ params.merge!(parse_list_options(options))
850
+ params[:query] = params.delete(:phrase) unless params[:phrase].nil?
851
+ @logs_interface.setopts(options)
852
+ if options[:dry_run]
853
+ print_dry_run @logs_interface.dry.cluster_logs(cluster['id'], params)
854
+ return
855
+ end
856
+ json_response = @logs_interface.cluster_logs(cluster['id'], params)
857
+ render_result = render_with_format(json_response, options, 'data')
858
+ return 0 if render_result
859
+
860
+ logs = json_response
861
+ title = "Cluster Logs: #{cluster['name']}"
862
+ subtitles = parse_list_subtitles(options)
863
+ if params[:query]
864
+ subtitles << "Search: #{params[:query]}".strip
865
+ end
866
+ # todo: startMs, endMs, sorts insteaad of sort..etc
867
+ print_h1 title, subtitles, options
868
+ if logs['data'].empty?
869
+ puts "#{cyan}No logs found.#{reset}"
870
+ else
871
+ logs['data'].reverse.each do |log_entry|
872
+ log_level = ''
873
+ case log_entry['level']
874
+ when 'INFO'
875
+ log_level = "#{blue}#{bold}INFO#{reset}"
876
+ when 'DEBUG'
877
+ log_level = "#{white}#{bold}DEBUG#{reset}"
878
+ when 'WARN'
879
+ log_level = "#{yellow}#{bold}WARN#{reset}"
880
+ when 'ERROR'
881
+ log_level = "#{red}#{bold}ERROR#{reset}"
882
+ when 'FATAL'
883
+ log_level = "#{red}#{bold}FATAL#{reset}"
884
+ end
885
+ puts "[#{log_entry['ts']}] #{log_level} - #{log_entry['message'].to_s.strip}"
886
+ end
887
+ print_results_pagination({'meta'=>{'total'=>json_response['total'],'size'=>json_response['data'].size,'max'=>(json_response['max'] || options[:max]),'offset'=>(json_response['offset'] || options[:offset] || 0)}})
888
+ end
889
+ print reset, "\n"
890
+ return 0
891
+ rescue RestClient::Exception => e
892
+ print_rest_exception(e, options)
893
+ exit 1
894
+ end
895
+ end
896
+
897
+ def update_permissions(args)
898
+ options = {}
899
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
900
+ opts.banner = subcommand_usage( "[cluster]")
901
+ add_perms_options(opts, options)
902
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
903
+ opts.footer = "Update a clusters permissions.\n" +
904
+ "[cluster] is required. This is the name or id of an existing cluster."
905
+ end
906
+
907
+ optparse.parse!(args)
908
+ if args.count != 1
909
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
910
+ end
911
+ connect(options)
912
+
913
+ begin
914
+ cluster = find_cluster_by_name_or_id(args[0])
915
+ return 1 if cluster.nil?
916
+
917
+ if options[:payload]
918
+ payload = options[:payload]
919
+ # support -O OPTION switch on top of --payload
920
+ if options[:options]
921
+ payload['permissions'] ||= {}
922
+ payload['permissions'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) })
923
+ end
924
+ else
925
+ payload = {"permissions" => prompt_permissions(options)}
926
+ end
927
+
928
+ @clusters_interface.setopts(options)
929
+ if options[:dry_run]
930
+ print_dry_run @clusters_interface.dry.update_permissions(cluster['id'], payload)
931
+ return
932
+ end
933
+ json_response = @clusters_interface.update_permissions(cluster['id'], payload)
934
+ if options[:json]
935
+ print JSON.pretty_generate(json_response)
936
+ print "\n"
937
+ elsif json_response['success']
938
+ get_args = [json_response["cluster"]["id"], '--permissions'] + (options[:remote] ? ["-r",options[:remote]] : []) + (options[:refresh_interval] ? ['--refresh', options[:refresh_interval].to_s] : [])
939
+ get(get_args)
940
+ else
941
+ print_rest_errors(json_response, options)
942
+ end
943
+ end
944
+ end
945
+
946
+ def list_workers(args)
947
+ options = {}
948
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
949
+ opts.banner = subcommand_usage( "[cluster]")
950
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
951
+ opts.footer = "List workers for a cluster.\n" +
952
+ "[cluster] is required. This is the name or id of an existing cluster."
953
+ end
954
+
955
+ optparse.parse!(args)
956
+ if args.count != 1
957
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
958
+ end
959
+ connect(options)
960
+ begin
961
+ cluster = find_cluster_by_name_or_id(args[0])
962
+ return 1 if cluster.nil?
963
+
964
+ params = {}
965
+ params.merge!(parse_list_options(options))
966
+ @clusters_interface.setopts(options)
967
+ if options[:dry_run]
968
+ print_dry_run @clusters_interface.dry.list_workers(cluster['id'], params)
969
+ return
970
+ end
971
+ json_response = @clusters_interface.list_workers(cluster['id'], params)
972
+
973
+ render_result = render_with_format(json_response, options, 'workers')
974
+ return 0 if render_result
975
+
976
+ title = "Morpheus Cluster Workers: #{cluster['name']}"
977
+ subtitles = []
978
+ subtitles += parse_list_subtitles(options)
979
+ print_h1 title, subtitles
980
+ workers = json_response['workers']
981
+ if workers.empty?
982
+ print yellow,"No workers found.",reset,"\n"
983
+ else
984
+ # more stuff to show here
985
+
986
+ servers = workers
987
+ multi_tenant = false
988
+
989
+ # print_servers_table(servers)
990
+ rows = servers.collect {|server|
991
+ stats = server['stats']
992
+
993
+ if !stats['maxMemory']
994
+ stats['maxMemory'] = stats['usedMemory'] + stats['freeMemory']
995
+ end
996
+ cpu_usage_str = !stats ? "" : generate_usage_bar((stats['cpuUsage']).to_f, 100, {max_bars: 10})
997
+ memory_usage_str = !stats ? "" : generate_usage_bar(stats['usedMemory'], stats['maxMemory'], {max_bars: 10})
998
+ storage_usage_str = !stats ? "" : generate_usage_bar(stats['usedStorage'], stats['maxStorage'], {max_bars: 10})
999
+ if options[:details]
1000
+ if stats['maxMemory'] && stats['maxMemory'].to_i != 0
1001
+ memory_usage_str = memory_usage_str + cyan + format_bytes_short(stats['usedMemory']).strip.rjust(8, ' ') + " / " + format_bytes_short(stats['maxMemory']).strip
1002
+ end
1003
+ if stats['maxStorage'] && stats['maxStorage'].to_i != 0
1004
+ storage_usage_str = storage_usage_str + cyan + format_bytes_short(stats['usedStorage']).strip.rjust(8, ' ') + " / " + format_bytes_short(stats['maxStorage']).strip
1005
+ end
1006
+ end
1007
+ row = {
1008
+ id: server['id'],
1009
+ tenant: server['account'] ? server['account']['name'] : server['accountId'],
1010
+ name: server['name'],
1011
+ platform: server['serverOs'] ? server['serverOs']['name'].upcase : 'N/A',
1012
+ cloud: server['zone'] ? server['zone']['name'] : '',
1013
+ type: server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged',
1014
+ nodes: server['containers'] ? server['containers'].size : '',
1015
+ status: format_server_status(server, cyan),
1016
+ power: format_server_power_state(server, cyan),
1017
+ cpu: cpu_usage_str + cyan,
1018
+ memory: memory_usage_str + cyan,
1019
+ storage: storage_usage_str + cyan
1020
+ }
1021
+ row
1022
+ }
1023
+ columns = [:id, :name, :type, :cloud, :nodes, :status, :power]
1024
+ if multi_tenant
1025
+ columns.insert(4, :tenant)
1026
+ end
1027
+ columns += [:cpu, :memory, :storage]
1028
+ # custom pretty table columns ...
1029
+ if options[:include_fields]
1030
+ columns = options[:include_fields]
1031
+ end
1032
+ print cyan
1033
+ print as_pretty_table(rows, columns, options)
1034
+ print reset
1035
+ print_results_pagination(json_response)
1036
+ end
1037
+ print reset,"\n"
1038
+ return 0
1039
+ rescue RestClient::Exception => e
1040
+ print_rest_exception(e, options)
1041
+ exit 1
1042
+ end
1043
+ end
1044
+
1045
+ def add_worker(args)
1046
+ options = {}
1047
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1048
+ opts.banner = subcommand_usage( "[cluster] [options]")
1049
+ opts.on("--name NAME", String, "Name of the new worker") do |val|
1050
+ options[:name] = val.to_s
1051
+ end
1052
+ opts.on("--description [TEXT]", String, "Description") do |val|
1053
+ options[:description] = val.to_s
1054
+ end
1055
+ add_server_options(opts, options)
1056
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
1057
+ opts.footer = "Add worker to a cluster.\n" +
1058
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
1059
+ "[name] is required. This is the name of the new worker."
1060
+ end
1061
+
1062
+ optparse.parse!(args)
1063
+ if args.count != 1
1064
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
1065
+ end
1066
+ connect(options)
1067
+
1068
+ begin
1069
+ cluster = find_cluster_by_name_or_id(args[0])
1070
+ return 1 if cluster.nil?
1071
+ payload = nil
1072
+ if options[:payload]
1073
+ payload = options[:payload]
1074
+ # support -O OPTION switch on top of --payload
1075
+ if options[:options]
1076
+ payload['server'] ||= {}
1077
+ payload['server'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) })
1078
+ end
1079
+ else
1080
+ server_payload = {'config' => {}}
1081
+
1082
+ # If not available add set type return
1083
+ layout = find_layout_by_id(cluster['layout']['id'])
1084
+
1085
+ # currently limiting to just worker types
1086
+ available_type_sets = layout['computeServers'].reject {|typeSet| !typeSet['dynamicCount']}
1087
+
1088
+ if available_type_sets.empty?
1089
+ print_red_alert "Cluster #{cluster['name']} has no available server types to add"
1090
+ exit 1
1091
+ else
1092
+ type_set = available_type_sets[0]
1093
+ end
1094
+
1095
+ # find servers within cluster
1096
+ server_matches = cluster['servers'].reject {|server| server['typeSet']['id'] != type_set['id']}
1097
+ server_type = find_server_type_by_id(server_matches.count > 0 ? server_matches[0]['computeServerType']['id'] : type_set['computeServerType']['id'])
1098
+ server_payload['serverType'] = {'id' => server_type['id']}
1099
+
1100
+ # Name
1101
+ if options[:name].empty?
1102
+ default_name = (server_matches.count ? server_matches[0]['name'] : nil) || cluster['name']
1103
+ default_name.delete_prefix!(type_set['namePrefix']) if !type_set['namePrefix'].empty?
1104
+ default_name = default_name[0..(default_name.index(type_set['nameSuffix']) - 1)] if !type_set['nameSuffix'].nil? && default_name.index(type_set['nameSuffix'])
1105
+ default_name = (type_set['namePrefix'] || '') + default_name + (type_set['nameSuffix'] || '') + '-' + (server_matches.count + 1).to_s
1106
+ server_payload['name'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => 'Name', 'required' => true, 'description' => 'Worker Name.', 'defaultValue' => default_name}],options[:options],@api_client,{})['name']
1107
+ else
1108
+ server_payload['name'] = options[:name]
1109
+ end
1110
+
1111
+ # Description
1112
+ server_payload['description'] = options[:description] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'description' => 'Worker Description'}], options[:options], @api_client)['description']
1113
+
1114
+ # Cloud
1115
+ available_clouds = options_interface.options_for_source('clouds', {groupId: cluster['site']['id'], clusterId: cluster['id'], ownerOnly: true})['data']
1116
+ cloud_id = nil
1117
+
1118
+ if options[:cloud]
1119
+ cloud = available_clouds.find {|it| it['value'].to_s == options[:cloud].to_s || it['name'].casecmp?(options[:cloud].to_s)}
1120
+
1121
+ if !cloud
1122
+ print_red_alert "Cloud #{options[:cloud]} is not a valid option for cluster #{cluster['name']}"
1123
+ exit 1
1124
+ end
1125
+ cloud_id = cloud['value']
1126
+ end
1127
+
1128
+ if !cloud_id
1129
+ default_cloud = available_clouds.find {|it| it['value'] == cluster['zone']['id']}
1130
+ cloud_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'fieldLabel' => 'Cloud', 'type' => 'select', 'selectOptions' => available_clouds, 'description' => 'Cloud', 'required' => true, 'defaultValue' => (default_cloud ? default_cloud['name'] : nil)}], options[:options], @api_client)['cloud']
1131
+ cloud_id = (default_cloud && cloud_id == default_cloud['name']) ? default_cloud['value'] : cloud_id
1132
+ end
1133
+
1134
+ server_payload['cloud'] = {'id' => cloud_id}
1135
+ service_plan = prompt_service_plan(cloud_id, server_type['provisionType'], options)
1136
+
1137
+ if service_plan
1138
+ server_payload['plan'] = {'code' => service_plan['code'], 'options' => prompt_service_plan_options(service_plan, options)}
1139
+ end
1140
+
1141
+ # resources (zone pools)
1142
+ cloud = @clouds_interface.get(cloud_id)['zone']
1143
+ cloud['zoneType'] = get_cloud_type(cloud['zoneType']['id'])
1144
+ group = @groups_interface.get(cluster['site']['id'])['group']
1145
+
1146
+ if resource_pool = prompt_resource_pool(cluster, cloud, service_plan, server_type['provisionType'], options)
1147
+ server_payload['config']['resourcePool'] = resource_pool['externalId']
1148
+ end
1149
+
1150
+ # Multi-disk / prompt for volumes
1151
+ volumes = options[:volumes] || prompt_volumes(service_plan, options.merge({'defaultAddFirstDataVolume': true}), @api_client, {zoneId: cloud['id'], siteId: group['id']})
1152
+
1153
+ if !volumes.empty?
1154
+ server_payload['volumes'] = volumes
1155
+ end
1156
+
1157
+ # Networks
1158
+ # NOTE: You must choose subnets in the same availability zone
1159
+ provision_type = server_type['provisionType'] || {}
1160
+ if provision_type && cloud['zoneType']['code'] != 'esxi'
1161
+ server_payload['networkInterfaces'] = options[:networkInterfaces] || prompt_network_interfaces(cloud['id'], server_type['provisionType']['id'], (resource_pool['id'] rescue nil), options)
1162
+ end
1163
+
1164
+ # Security Groups
1165
+ server_payload['securityGroups'] = prompt_security_groups_by_cloud(cloud, provision_type, resource_pool, options)
1166
+
1167
+ # Options / Custom Config
1168
+ option_type_list = (server_type['optionTypes'].reject { |type|
1169
+ !type['enabled'] || type['fieldComponent'] ||
1170
+ (['provisionType.vmware.host', 'provisionType.scvmm.host'].include?(type['code']) && cloud['config']['hideHostSelection'] == 'on') || # should this be truthy?
1171
+ (type['fieldContext'] == 'instance.networkDomain' && type['fieldName'] == 'id')
1172
+ } rescue [])
1173
+
1174
+ # remove volume options if volumes were configured
1175
+ if !server_payload['volumes'].empty?
1176
+ option_type_list = reject_volume_option_types(option_type_list)
1177
+ end
1178
+ # remove networkId option if networks were configured above
1179
+ if !server_payload['networkInterfaces'].empty?
1180
+ option_type_list = reject_networking_option_types(option_type_list)
1181
+ end
1182
+
1183
+ server_payload.deep_merge!(Morpheus::Cli::OptionTypes.prompt(option_type_list, options[:options], @api_client, {zoneId: cloud['id'], siteId: group['id'], layoutId: layout['id']}))
1184
+
1185
+ # Create User
1186
+ if !options[:createUser].nil?
1187
+ server_payload['config']['createUser'] = options[:createUser]
1188
+ elsif !options[:no_prompt]
1189
+ server_payload['config']['createUser'] = (current_user['windowsUsername'] || current_user['linuxUsername']) && Morpheus::Cli::OptionTypes.confirm("Create Your User?", {:default => true})
1190
+ end
1191
+
1192
+ # User Groups
1193
+ userGroup = options[:userGroup] ? find_user_group_by_name_or_id(nil, options[:userGroup]) : nil
1194
+
1195
+ if userGroup
1196
+ server_payload['userGroup'] = userGroup
1197
+ elsif !options[:no_prompt]
1198
+ userGroupId = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'userGroupId', 'fieldLabel' => 'User Group', 'type' => 'select', 'required' => false, 'optionSource' => 'userGroups'}], options[:options], @api_client, {})['userGroupId']
1199
+
1200
+ if userGroupId
1201
+ server_payload['userGroup'] = {'id' => userGroupId}
1202
+ end
1203
+ end
1204
+
1205
+ # Host / Domain
1206
+ server_payload['networkDomain'] = options[:domain] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'networkDomain', 'fieldLabel' => 'Network Domain', 'type' => 'select', 'required' => false, 'optionSource' => 'networkDomains'}], options[:options], @api_client, {})['networkDomain']
1207
+ server_payload['hostname'] = options[:hostname] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'hostname', 'fieldLabel' => 'Hostname', 'type' => 'text', 'required' => true, 'description' => 'Hostname', 'defaultValue' => server_payload['name']}], options[:options], @api_client)['hostname']
1208
+
1209
+ # Workflow / Automation
1210
+ if !options[:no_prompt]
1211
+ task_set_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'taskSet', 'fieldLabel' => 'Workflow', 'type' => 'select', 'required' => false, 'optionSource' => 'taskSets'}], options[:options], @api_client, {'phase' => 'postProvision', 'taskSetType' => 'provision'})['taskSet']
1212
+
1213
+ if task_set_id
1214
+ server_payload['taskSet'] = {'id' => task_set_id}
1215
+ end
1216
+ end
1217
+ payload = {'server' => server_payload}
1218
+ end
1219
+
1220
+ @clusters_interface.setopts(options)
1221
+ if options[:dry_run]
1222
+ print_dry_run @clusters_interface.dry.add_server(cluster['id'], payload)
1223
+ return
1224
+ end
1225
+ json_response = @clusters_interface.add_server(cluster['id'], payload)
1226
+ if options[:json]
1227
+ puts as_json(json_response)
1228
+ elsif json_response['success']
1229
+ print_green_success "Added worker to cluster #{cluster['name']}"
1230
+ #get_args = [json_response["cluster"]["id"]] + (options[:remote] ? ["-r",options[:remote]] : [])
1231
+ #get(get_args)
1232
+ end
1233
+ return 0
1234
+ rescue RestClient::Exception => e
1235
+ print_rest_exception(e, options)
1236
+ exit 1
1237
+ end
1238
+ end
1239
+
1240
+ def list_masters(args)
1241
+ options = {}
1242
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1243
+ opts.banner = subcommand_usage( "[cluster]")
1244
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
1245
+ opts.footer = "List masters for a cluster.\n" +
1246
+ "[cluster] is required. This is the name or id of an existing cluster."
1247
+ end
1248
+
1249
+ optparse.parse!(args)
1250
+ if args.count != 1
1251
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
1252
+ end
1253
+ connect(options)
1254
+ begin
1255
+ cluster = find_cluster_by_name_or_id(args[0])
1256
+ return 1 if cluster.nil?
1257
+
1258
+ params = {}
1259
+ params.merge!(parse_list_options(options))
1260
+ @clusters_interface.setopts(options)
1261
+ if options[:dry_run]
1262
+ print_dry_run @clusters_interface.dry.list_masters(cluster['id'], params)
1263
+ return
1264
+ end
1265
+ json_response = @clusters_interface.list_masters(cluster['id'], params)
1266
+
1267
+ render_result = render_with_format(json_response, options, 'masters')
1268
+ return 0 if render_result
1269
+
1270
+ title = "Morpheus Cluster Masters: #{cluster['name']}"
1271
+ subtitles = []
1272
+ subtitles += parse_list_subtitles(options)
1273
+ print_h1 title, subtitles
1274
+ masters = json_response['masters']
1275
+ if masters.empty?
1276
+ print yellow,"No masters found.",reset,"\n"
1277
+ else
1278
+ # more stuff to show here
1279
+
1280
+ servers = masters
1281
+ multi_tenant = false
1282
+
1283
+ # print_servers_table(servers)
1284
+ rows = servers.collect {|server|
1285
+ stats = server['stats']
1286
+
1287
+ if !stats['maxMemory']
1288
+ stats['maxMemory'] = stats['usedMemory'] + stats['freeMemory']
1289
+ end
1290
+ cpu_usage_str = !stats ? "" : generate_usage_bar((stats['cpuUsage']).to_f, 100, {max_bars: 10})
1291
+ memory_usage_str = !stats ? "" : generate_usage_bar(stats['usedMemory'], stats['maxMemory'], {max_bars: 10})
1292
+ storage_usage_str = !stats ? "" : generate_usage_bar(stats['usedStorage'], stats['maxStorage'], {max_bars: 10})
1293
+ if options[:details]
1294
+ if stats['maxMemory'] && stats['maxMemory'].to_i != 0
1295
+ memory_usage_str = memory_usage_str + cyan + format_bytes_short(stats['usedMemory']).strip.rjust(8, ' ') + " / " + format_bytes_short(stats['maxMemory']).strip
1296
+ end
1297
+ if stats['maxStorage'] && stats['maxStorage'].to_i != 0
1298
+ storage_usage_str = storage_usage_str + cyan + format_bytes_short(stats['usedStorage']).strip.rjust(8, ' ') + " / " + format_bytes_short(stats['maxStorage']).strip
1299
+ end
1300
+ end
1301
+ row = {
1302
+ id: server['id'],
1303
+ tenant: server['account'] ? server['account']['name'] : server['accountId'],
1304
+ name: server['name'],
1305
+ platform: server['serverOs'] ? server['serverOs']['name'].upcase : 'N/A',
1306
+ cloud: server['zone'] ? server['zone']['name'] : '',
1307
+ type: server['computeServerType'] ? server['computeServerType']['name'] : 'unmanaged',
1308
+ nodes: server['containers'] ? server['containers'].size : '',
1309
+ status: format_server_status(server, cyan),
1310
+ power: format_server_power_state(server, cyan),
1311
+ cpu: cpu_usage_str + cyan,
1312
+ memory: memory_usage_str + cyan,
1313
+ storage: storage_usage_str + cyan
1314
+ }
1315
+ row
1316
+ }
1317
+ columns = [:id, :name, :type, :cloud, :nodes, :status, :power]
1318
+ if multi_tenant
1319
+ columns.insert(4, :tenant)
1320
+ end
1321
+ columns += [:cpu, :memory, :storage]
1322
+ # custom pretty table columns ...
1323
+ if options[:include_fields]
1324
+ columns = options[:include_fields]
1325
+ end
1326
+ print cyan
1327
+ print as_pretty_table(rows, columns, options)
1328
+ print reset
1329
+ print_results_pagination(json_response)
1330
+ end
1331
+ print reset,"\n"
1332
+ return 0
1333
+ rescue RestClient::Exception => e
1334
+ print_rest_exception(e, options)
1335
+ exit 1
1336
+ end
1337
+ end
1338
+
1339
+ def list_volumes(args)
1340
+ options = {}
1341
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1342
+ opts.banner = subcommand_usage( "[cluster]")
1343
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
1344
+ opts.footer = "List volumes for a cluster.\n" +
1345
+ "[cluster] is required. This is the name or id of an existing cluster."
1346
+ end
1347
+
1348
+ optparse.parse!(args)
1349
+ if args.count != 1
1350
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
1351
+ end
1352
+ connect(options)
1353
+ begin
1354
+ cluster = find_cluster_by_name_or_id(args[0])
1355
+ return 1 if cluster.nil?
1356
+
1357
+ params = {}
1358
+ params.merge!(parse_list_options(options))
1359
+ @clusters_interface.setopts(options)
1360
+ if options[:dry_run]
1361
+ print_dry_run @clusters_interface.dry.list_volumes(cluster['id'], params)
1362
+ return
1363
+ end
1364
+ json_response = @clusters_interface.list_volumes(cluster['id'], params)
1365
+
1366
+ render_result = render_with_format(json_response, options, 'volumes')
1367
+ return 0 if render_result
1368
+
1369
+ title = "Morpheus Cluster Volumes: #{cluster['name']}"
1370
+ subtitles = []
1371
+ subtitles += parse_list_subtitles(options)
1372
+ print_h1 title, subtitles
1373
+ volumes = json_response['volumes']
1374
+ if volumes.empty?
1375
+ print yellow,"No volumes found.",reset,"\n"
1376
+ else
1377
+ # more stuff to show here
1378
+ rows = volumes.collect do |ns|
1379
+ {
1380
+ id: ns['id'],
1381
+ name: ns['name'],
1382
+ description: ns['description'],
1383
+ status: ns['status'],
1384
+ active: ns['active'],
1385
+ cluster: cluster['name']
1386
+ }
1387
+ end
1388
+ columns = [
1389
+ :id, :name, :description, :status, :active, :cluster => lambda { |it| cluster['name'] }
1390
+ ]
1391
+ print as_pretty_table(rows, columns, options)
1392
+ end
1393
+ print reset,"\n"
1394
+ return 0
1395
+ rescue RestClient::Exception => e
1396
+ print_rest_exception(e, options)
1397
+ exit 1
1398
+ end
1399
+ end
1400
+
1401
+ def remove_volume(args)
1402
+ params = {}
1403
+ options = {}
1404
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1405
+ opts.banner = subcommand_usage("[cluster] [volume]")
1406
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
1407
+ opts.footer = "Delete a volume within a cluster.\n" +
1408
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
1409
+ "[volume] is required. This is the name or id of an existing volume."
1410
+ end
1411
+ optparse.parse!(args)
1412
+ if args.count != 2
1413
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
1414
+ end
1415
+ connect(options)
1416
+
1417
+ begin
1418
+ cluster = find_cluster_by_name_or_id(args[0])
1419
+ return 1 if cluster.nil?
1420
+ volume_id = args[1]
1421
+
1422
+ if volume_id.empty?
1423
+ raise_command_error "missing required volume parameter"
1424
+ end
1425
+
1426
+ volume = find_volume_by_name_or_id(cluster['id'], volume_id)
1427
+ if volume.nil?
1428
+ print_red_alert "Volume not found for '#{volume_id}'"
1429
+ return 1
1430
+ end
1431
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the cluster volume '#{volume['name'] || volume['id']}'?", options)
1432
+ return 9, "aborted command"
1433
+ end
1434
+
1435
+ @clusters_interface.setopts(options)
1436
+ if options[:dry_run]
1437
+ print_dry_run @clusters_interface.dry.destroy_volume(cluster['id'], volume['id'], params)
1438
+ return
1439
+ end
1440
+ json_response = @clusters_interface.destroy_volume(cluster['id'], volume['id'], params)
1441
+ if options[:json]
1442
+ print JSON.pretty_generate(json_response)
1443
+ print "\n"
1444
+ elsif !options[:quiet]
1445
+ print_red_alert "Error removing volume #{volume['name']} from cluster #{cluster['name']}: #{json_response['msg']}" if json_response['success'] == false
1446
+ print_green_success "Volume #{volume['name']} is being removed from cluster #{cluster['name']}..." if json_response['success'] == true
1447
+ end
1448
+ rescue RestClient::Exception => e
1449
+ print_rest_exception(e, options)
1450
+ exit 1
1451
+ end
1452
+ end
1453
+
1454
+ def list_services(args)
1455
+ options = {}
1456
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1457
+ opts.banner = subcommand_usage( "[cluster]")
1458
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
1459
+ opts.footer = "List services for a cluster.\n" +
1460
+ "[cluster] is required. This is the name or id of an existing cluster."
1461
+ end
1462
+
1463
+ optparse.parse!(args)
1464
+ if args.count != 1
1465
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
1466
+ end
1467
+ connect(options)
1468
+ begin
1469
+ cluster = find_cluster_by_name_or_id(args[0])
1470
+ return 1 if cluster.nil?
1471
+
1472
+ params = {}
1473
+ params.merge!(parse_list_options(options))
1474
+ @clusters_interface.setopts(options)
1475
+ if options[:dry_run]
1476
+ print_dry_run @clusters_interface.dry.list_services(cluster['id'], params)
1477
+ return
1478
+ end
1479
+ json_response = @clusters_interface.list_services(cluster['id'], params)
1480
+
1481
+ render_result = render_with_format(json_response, options, 'volumes')
1482
+ return 0 if render_result
1483
+
1484
+ title = "Morpheus Cluster Services: #{cluster['name']}"
1485
+ subtitles = []
1486
+ subtitles += parse_list_subtitles(options)
1487
+ print_h1 title, subtitles
1488
+ services = json_response['services']
1489
+ if services.empty?
1490
+ print yellow,"No services found.",reset,"\n"
1491
+ else
1492
+ # more stuff to show here
1493
+ rows = services.collect do |service|
1494
+ {
1495
+ id: service['id'],
1496
+ name: service['name'],
1497
+ type: service['type'],
1498
+ externalIp: service['externalIp'],
1499
+ externalPort: service['externalPort'],
1500
+ internalPort: service['internalPort'],
1501
+ status: service['status'],
1502
+ cluster: cluster['name']
1503
+ }
1504
+ end
1505
+ columns = [
1506
+ :id, :name, :type, :externalIp, :externalPort, :internalPort, :status, :cluster => lambda { |it| cluster['name'] }
1507
+ ]
1508
+ print as_pretty_table(rows, columns, options)
1509
+ end
1510
+ print reset,"\n"
1511
+ return 0
1512
+ rescue RestClient::Exception => e
1513
+ print_rest_exception(e, options)
1514
+ exit 1
1515
+ end
1516
+ end
1517
+
1518
+ def remove_service(args)
1519
+ params = {}
1520
+ options = {}
1521
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1522
+ opts.banner = subcommand_usage("[cluster] [service]")
1523
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
1524
+ opts.footer = "Delete a service within a cluster.\n" +
1525
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
1526
+ "[service] is required. This is the name or id of an existing service."
1527
+ end
1528
+ optparse.parse!(args)
1529
+ if args.count != 2
1530
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
1531
+ end
1532
+ connect(options)
1533
+
1534
+ begin
1535
+ cluster = find_cluster_by_name_or_id(args[0])
1536
+ return 1 if cluster.nil?
1537
+ service_id = args[1]
1538
+
1539
+ if service_id.empty?
1540
+ raise_command_error "missing required service parameter"
1541
+ end
1542
+
1543
+ service = find_service_by_name_or_id(cluster['id'], service_id)
1544
+ if service.nil?
1545
+ print_red_alert "Service not found by id '#{service_id}'"
1546
+ return 1
1547
+ end
1548
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the cluster service '#{service['name'] || service['id']}'?", options)
1549
+ return 9, "aborted command"
1550
+ end
1551
+
1552
+ @clusters_interface.setopts(options)
1553
+ if options[:dry_run]
1554
+ print_dry_run @clusters_interface.dry.destroy_service(cluster['id'], service['id'], params)
1555
+ return
1556
+ end
1557
+ json_response = @clusters_interface.destroy_service(cluster['id'], service['id'], params)
1558
+ if options[:json]
1559
+ print JSON.pretty_generate(json_response)
1560
+ print "\n"
1561
+ elsif !options[:quiet]
1562
+ print_red_alert "Error removing service #{service['name']} from cluster #{cluster['name']}: #{json_response['msg']}" if json_response['success'] == false
1563
+ print_green_success "Service #{service['name']} is being removed from cluster #{cluster['name']}..." if json_response['success'] == true
1564
+ end
1565
+ rescue RestClient::Exception => e
1566
+ print_rest_exception(e, options)
1567
+ exit 1
1568
+ end
1569
+ end
1570
+
1571
+ def list_jobs(args)
1572
+ options = {}
1573
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1574
+ opts.banner = subcommand_usage( "[cluster]")
1575
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
1576
+ opts.footer = "List jobs for a cluster.\n" +
1577
+ "[cluster] is required. This is the name or id of an existing cluster."
1578
+ end
1579
+
1580
+ optparse.parse!(args)
1581
+ if args.count != 1
1582
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
1583
+ end
1584
+ connect(options)
1585
+ begin
1586
+ cluster = find_cluster_by_name_or_id(args[0])
1587
+ return 1 if cluster.nil?
1588
+
1589
+ params = {}
1590
+ params.merge!(parse_list_options(options))
1591
+ @clusters_interface.setopts(options)
1592
+ if options[:dry_run]
1593
+ print_dry_run @clusters_interface.dry.list_jobs(cluster['id'], params)
1594
+ return
1595
+ end
1596
+ json_response = @clusters_interface.list_jobs(cluster['id'], params)
1597
+
1598
+ render_result = render_with_format(json_response, options, 'volumes')
1599
+ return 0 if render_result
1600
+
1601
+ title = "Morpheus Cluster Jobs: #{cluster['name']}"
1602
+ subtitles = []
1603
+ subtitles += parse_list_subtitles(options)
1604
+ print_h1 title, subtitles
1605
+ jobs = json_response['jobs']
1606
+ if jobs.empty?
1607
+ print yellow,"No jobs found.",reset,"\n"
1608
+ else
1609
+ # more stuff to show here
1610
+ rows = jobs.collect do |job|
1611
+ {
1612
+ id: job['id'],
1613
+ status: job['type'],
1614
+ namespace: job['namespace'],
1615
+ name: job['name'],
1616
+ lastRun: format_local_dt(job['lastRun']),
1617
+ cluster: cluster['name']
1618
+ }
1619
+ end
1620
+ columns = [
1621
+ :id, :status, :namespace, :name, :lastRun, :cluster => lambda { |it| cluster['name'] }
1622
+ ]
1623
+ print as_pretty_table(rows, columns, options)
1624
+ end
1625
+ print reset,"\n"
1626
+ return 0
1627
+ rescue RestClient::Exception => e
1628
+ print_rest_exception(e, options)
1629
+ exit 1
1630
+ end
1631
+ end
1632
+
1633
+ def remove_job(args)
1634
+ params = {}
1635
+ options = {}
1636
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1637
+ opts.banner = subcommand_usage("[cluster] [job]")
1638
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
1639
+ opts.footer = "Delete a job within a cluster.\n" +
1640
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
1641
+ "[job] is required. This is the name or id of an existing job."
1642
+ end
1643
+ optparse.parse!(args)
1644
+ if args.count != 2
1645
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
1646
+ end
1647
+ connect(options)
1648
+
1649
+ begin
1650
+ cluster = find_cluster_by_name_or_id(args[0])
1651
+ return 1 if cluster.nil?
1652
+ job_id = args[1]
1653
+
1654
+ if job_id.empty?
1655
+ raise_command_error "missing required job parameter"
1656
+ end
1657
+
1658
+ job = find_job_by_name_or_id(cluster['id'], job_id)
1659
+ if job.nil?
1660
+ print_red_alert "Job not found by id '#{job_id}'"
1661
+ return 1
1662
+ end
1663
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the cluster job '#{job['name'] || job['id']}'?", options)
1664
+ return 9, "aborted command"
1665
+ end
1666
+
1667
+ @clusters_interface.setopts(options)
1668
+ if options[:dry_run]
1669
+ print_dry_run @clusters_interface.dry.destroy_job(cluster['id'], job['id'], params)
1670
+ return
1671
+ end
1672
+ json_response = @clusters_interface.destroy_job(cluster['id'], job['id'], params)
1673
+ if options[:json]
1674
+ print JSON.pretty_generate(json_response)
1675
+ print "\n"
1676
+ elsif !options[:quiet]
1677
+ print_red_alert "Error removing job #{job['name']} from cluster #{cluster['name']}: #{json_response['msg']}" if json_response['success'] == false
1678
+ print_green_success "Job #{job['name']} is being removed from cluster #{cluster['name']}..." if json_response['success'] == true
1679
+ end
1680
+ rescue RestClient::Exception => e
1681
+ print_rest_exception(e, options)
1682
+ exit 1
1683
+ end
1684
+ end
1685
+
1686
+ def list_containers(args)
1687
+ options = {}
1688
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1689
+ opts.banner = subcommand_usage( "[cluster]")
1690
+ opts.on("--resource-level LEVEL", String, "Resource Level") do |val|
1691
+ options[:resourceLevel] = val.to_s
1692
+ end
1693
+ opts.on("--worker WORKER", String, "Worker") do |val|
1694
+ options[:worker] = val
1695
+ end
1696
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
1697
+ opts.footer = "List containers for a cluster.\n" +
1698
+ "[cluster] is required. This is the name or id of an existing cluster."
1699
+ end
1700
+
1701
+ optparse.parse!(args)
1702
+ if args.count != 1
1703
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
1704
+ end
1705
+ connect(options)
1706
+ begin
1707
+ cluster = find_cluster_by_name_or_id(args[0])
1708
+ return 1 if cluster.nil?
1709
+ if options[:worker]
1710
+ worker = find_host_by_name_or_id(options[:worker])
1711
+ return 1 if worker.nil?
1712
+ params['workerId'] = worker['id']
1713
+ end
1714
+ params = {}
1715
+ params.merge!(parse_list_options(options))
1716
+ params['resourceLevel'] = options[:resourceLevel] if !options[:resourceLevel].nil?
1717
+ @clusters_interface.setopts(options)
1718
+ if options[:dry_run]
1719
+ print_dry_run @clusters_interface.dry.list_containers(cluster['id'], params)
1720
+ return
1721
+ end
1722
+ json_response = @clusters_interface.list_containers(cluster['id'], params)
1723
+
1724
+ render_result = render_with_format(json_response, options, 'containers')
1725
+ return 0 if render_result
1726
+
1727
+ title = "Morpheus Cluster Containers: #{cluster['name']}"
1728
+ subtitles = []
1729
+ subtitles += parse_list_subtitles(options)
1730
+ print_h1 title, subtitles
1731
+ containers = json_response['containers']
1732
+ if containers.empty?
1733
+ print yellow,"No containers found.",reset,"\n"
1734
+ else
1735
+ # more stuff to show here
1736
+ rows = containers.collect do |it|
1737
+ {
1738
+ id: it['id'],
1739
+ status: it['status'],
1740
+ name: it['name'],
1741
+ instance: it['instance'].nil? ? '' : it['instance']['name'],
1742
+ type: it['containerType'].nil? ? '' : it['containerType']['name'],
1743
+ location: it['ip'],
1744
+ cluster: cluster['name']
1745
+ }
1746
+ end
1747
+ columns = [
1748
+ :id, :status, :name, :instance, :type, :location, :cluster => lambda { |it| cluster['name'] }
1749
+ ]
1750
+ print as_pretty_table(rows, columns, options)
1751
+ end
1752
+ print reset,"\n"
1753
+ return 0
1754
+ rescue RestClient::Exception => e
1755
+ print_rest_exception(e, options)
1756
+ exit 1
1757
+ end
1758
+ end
1759
+
1760
+ def remove_container(args)
1761
+ params = {}
1762
+ options = {}
1763
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1764
+ opts.banner = subcommand_usage("[cluster] [container]")
1765
+ opts.on( '-f', '--force', "Force Delete" ) do
1766
+ options[:force] = 'on'
1767
+ end
1768
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
1769
+ opts.footer = "Delete a container within a cluster.\n" +
1770
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
1771
+ "[container] is required. This is the name or id of an existing container."
1772
+ end
1773
+ optparse.parse!(args)
1774
+ if args.count != 2
1775
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
1776
+ end
1777
+ connect(options)
1778
+
1779
+ begin
1780
+ cluster = find_cluster_by_name_or_id(args[0])
1781
+ return 1 if cluster.nil?
1782
+ container_id = args[1]
1783
+
1784
+ if container_id.empty?
1785
+ raise_command_error "missing required container parameter"
1786
+ end
1787
+
1788
+ container = find_container_by_name_or_id(cluster['id'], container_id)
1789
+ if container.nil?
1790
+ print_red_alert "Container not found by id '#{container_id}'"
1791
+ return 1
1792
+ end
1793
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the cluster container '#{container['name'] || container['id']}'?", options)
1794
+ return 9, "aborted command"
1795
+ end
1796
+
1797
+ if !options[:force].nil?
1798
+ params['force'] = options[:force]
1799
+ end
1800
+
1801
+ @clusters_interface.setopts(options)
1802
+ if options[:dry_run]
1803
+ print_dry_run @clusters_interface.dry.destroy_container(cluster['id'], container['id'], params)
1804
+ return
1805
+ end
1806
+ json_response = @clusters_interface.destroy_container(cluster['id'], container['id'], params)
1807
+ if options[:json]
1808
+ print JSON.pretty_generate(json_response)
1809
+ print "\n"
1810
+ elsif !options[:quiet]
1811
+ print_red_alert "Error removing container #{container['name']} from cluster #{cluster['name']}: #{json_response['msg']}" if json_response['success'] == false
1812
+ print_green_success "container #{container['name']} is being removed from cluster #{cluster['name']}..." if json_response['success'] == true
1813
+ end
1814
+ rescue RestClient::Exception => e
1815
+ print_rest_exception(e, options)
1816
+ exit 1
1817
+ end
1818
+ end
1819
+
1820
+ def restart_container(args)
1821
+ params = {}
1822
+ options = {}
1823
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
1824
+ opts.banner = subcommand_usage("[cluster] [container]")
1825
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
1826
+ opts.footer = "Restart a container within a cluster.\n" +
1827
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
1828
+ "[container] is required. This is the name or id of an existing container."
1829
+ end
1830
+ optparse.parse!(args)
1831
+ if args.count != 2
1832
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
1833
+ end
1834
+ connect(options)
1835
+
1836
+ begin
1837
+ cluster = find_cluster_by_name_or_id(args[0])
1838
+ return 1 if cluster.nil?
1839
+ container_id = args[1]
1840
+
1841
+ if container_id.empty?
1842
+ raise_command_error "missing required container parameter"
1843
+ end
1844
+
1845
+ container = find_container_by_name_or_id(cluster['id'], container_id)
1846
+ if container.nil?
1847
+ print_red_alert "Container not found by id '#{container_id}'"
1848
+ return 1
1849
+ end
1850
+
1851
+ @clusters_interface.setopts(options)
1852
+ if options[:dry_run]
1853
+ print_dry_run @clusters_interface.dry.restart_container(cluster['id'], container['id'], params)
1854
+ return
1855
+ end
1856
+ json_response = @clusters_interface.restart_container(cluster['id'], container['id'], params)
1857
+ if options[:json]
1858
+ print JSON.pretty_generate(json_response)
1859
+ print "\n"
1860
+ elsif !options[:quiet]
1861
+ print_red_alert "Error restarting container #{container['name']} for cluster #{cluster['name']}: #{json_response['msg']}" if json_response['success'] == false
1862
+ print_green_success "Container #{container['name']} is restarting for cluster #{cluster['name']}..." if json_response['success'] == true
1863
+ end
1864
+ rescue RestClient::Exception => e
1865
+ print_rest_exception(e, options)
1866
+ exit 1
1867
+ end
1868
+ end
1869
+
1870
+ def _list_container_groups(args, options, resource_type)
1871
+ begin
1872
+ cluster = find_cluster_by_name_or_id(args[0])
1873
+ return 1 if cluster.nil?
1874
+
1875
+ params = {}
1876
+ params.merge!(parse_list_options(options))
1877
+ params['resourceLevel'] = options[:resourceLevel] if !options[:resourceLevel].nil?
1878
+ @clusters_interface.setopts(options)
1879
+ if options[:dry_run]
1880
+ print_dry_run @clusters_interface.dry.list_container_groups(cluster['id'], resource_type, params)
1881
+ return
1882
+ end
1883
+ json_response = @clusters_interface.list_container_groups(cluster['id'], resource_type, params)
1884
+
1885
+ render_result = render_with_format(json_response, options, 'containers')
1886
+ return 0 if render_result
1887
+
1888
+ title = "Morpheus Cluster #{resource_type.capitalize}s: #{cluster['name']}"
1889
+ subtitles = []
1890
+ subtitles += parse_list_subtitles(options)
1891
+ print_h1 title, subtitles
1892
+ container_groups = json_response["#{resource_type}s"]
1893
+ if container_groups.empty?
1894
+ print yellow,"No #{resource_type}s found.",reset,"\n"
1895
+ else
1896
+ # more stuff to show here
1897
+ rows = container_groups.collect do |it|
1898
+ stats = it['stats']
1899
+ cpu_usage_str = generate_usage_bar((it['totalCpuUsage']).to_f, 100, {max_bars: 10})
1900
+ memory_usage_str = !stats ? "" : generate_usage_bar(stats['usedMemory'], stats['maxMemory'], {max_bars: 10})
1901
+ storage_usage_str = !stats ? "" : generate_usage_bar(stats['usedStorage'], stats['maxStorage'], {max_bars: 10})
1902
+ {
1903
+ id: it['id'],
1904
+ status: it['status'],
1905
+ name: it['name'],
1906
+ cpu: cpu_usage_str + cyan,
1907
+ memory: memory_usage_str + cyan,
1908
+ storage: storage_usage_str + cyan
1909
+ }
1910
+ end
1911
+ columns = [
1912
+ :id, :status, :name, :cpu, :memory, :storage
1913
+ ]
1914
+ print as_pretty_table(rows, columns, options)
1915
+ end
1916
+ print reset,"\n"
1917
+ return 0
1918
+ rescue RestClient::Exception => e
1919
+ print_rest_exception(e, options)
1920
+ exit 1
1921
+ end
1922
+ end
1923
+
1924
+ def _remove_container_group(args, options, resource_type)
1925
+ begin
1926
+ cluster = find_cluster_by_name_or_id(args[0])
1927
+ return 1 if cluster.nil?
1928
+ container_group_id = args[1]
1929
+
1930
+ if container_group_id.empty?
1931
+ raise_command_error "missing required container parameter"
1932
+ end
1933
+
1934
+ container_group = find_container_group_by_name_or_id(cluster['id'], resource_type, container_group_id)
1935
+ if container_group.nil?
1936
+ print_red_alert "#{resource_type.capitalize} not found by id '#{container_group_id}'"
1937
+ return 1
1938
+ end
1939
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the cluster #{resource_type} '#{container_group['name'] || container_group['id']}'?", options)
1940
+ return 9, "aborted command"
1941
+ end
1942
+
1943
+ params = {}
1944
+ params.merge!(parse_list_options(options))
1945
+
1946
+ if !options[:force].nil?
1947
+ params['force'] = options[:force]
1948
+ end
1949
+
1950
+ @clusters_interface.setopts(options)
1951
+ if options[:dry_run]
1952
+ print_dry_run @clusters_interface.dry.destroy_container_group(cluster['id'], container_group['id'], resource_type, params)
1953
+ return
1954
+ end
1955
+ json_response = @clusters_interface.destroy_container_group(cluster['id'], container_group['id'], resource_type, params)
1956
+ if options[:json]
1957
+ print JSON.pretty_generate(json_response)
1958
+ print "\n"
1959
+ elsif !options[:quiet]
1960
+ print_red_alert "Error removing #{resource_type} #{container_group['name']} from cluster #{cluster['name']}: #{json_response['msg']}" if json_response['success'] == false
1961
+ print_green_success "#{resource_type.capitalize} #{container_group['name']} is being removed from cluster #{cluster['name']}..." if json_response['success'] == true
1962
+ end
1963
+ rescue RestClient::Exception => e
1964
+ print_rest_exception(e, options)
1965
+ exit 1
1966
+ end
1967
+ end
1968
+
1969
+ def _restart_container_group(args, options, resource_type)
1970
+ begin
1971
+ cluster = find_cluster_by_name_or_id(args[0])
1972
+ return 1 if cluster.nil?
1973
+ container_group_id = args[1]
1974
+
1975
+ if container_group_id.empty?
1976
+ raise_command_error "missing required container parameter"
1977
+ end
1978
+
1979
+ container_group = find_container_group_by_name_or_id(cluster['id'], resource_type, container_group_id)
1980
+ if container_group.nil?
1981
+ print_red_alert "#{resource_type.capitalize} not found by id '#{container_group_id}'"
1982
+ return 1
1983
+ end
1984
+
1985
+ params = {}
1986
+ params.merge!(parse_list_options(options))
1987
+
1988
+ if !options[:force].nil?
1989
+ params['force'] = options[:force]
1990
+ end
1991
+
1992
+ @clusters_interface.setopts(options)
1993
+ if options[:dry_run]
1994
+ print_dry_run @clusters_interface.dry.restart_container_group(cluster['id'], container_group['id'], resource_type, params)
1995
+ return
1996
+ end
1997
+ json_response = @clusters_interface.restart_container_group(cluster['id'], container_group['id'], resource_type, params)
1998
+ if options[:json]
1999
+ print JSON.pretty_generate(json_response)
2000
+ print "\n"
2001
+ elsif !options[:quiet]
2002
+ print_red_alert "Error restarting #{resource_type} #{container_group['name']} from cluster #{cluster['name']}: #{json_response['msg']}" if json_response['success'] == false
2003
+ print_green_success "#{resource_type.capitalize} #{container_group['name']} is being restarted for cluster #{cluster['name']}..." if json_response['success'] == true
2004
+ end
2005
+ rescue RestClient::Exception => e
2006
+ print_rest_exception(e, options)
2007
+ exit 1
2008
+ end
2009
+ end
2010
+
2011
+ def list_deployments(args)
2012
+ resource_type = 'deployment'
2013
+ options = {}
2014
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2015
+ opts.banner = subcommand_usage( "[cluster]")
2016
+ opts.on("--resource-level LEVEL", String, "Resource Level") do |val|
2017
+ options[:resourceLevel] = val.to_s
2018
+ end
2019
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
2020
+ opts.footer = "List #{resource_type}s for a cluster.\n" +
2021
+ "[cluster] is required. This is the name or id of an existing cluster."
2022
+ end
2023
+ optparse.parse!(args)
2024
+ if args.count != 1
2025
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
2026
+ end
2027
+ connect(options)
2028
+ _list_container_groups(args, options,resource_type)
2029
+ end
2030
+
2031
+ def remove_deployment(args)
2032
+ resource_type = 'deployment'
2033
+ options = {}
2034
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2035
+ opts.banner = subcommand_usage("[cluster] [#{resource_type}]")
2036
+ opts.on( '-f', '--force', "Force Delete" ) do
2037
+ options[:force] = 'on'
2038
+ end
2039
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
2040
+ opts.footer = "Delete a #{resource_type} within a cluster.\n" +
2041
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
2042
+ "[#{resource_type}] is required. This is the name or id of an existing #{resource_type}."
2043
+ end
2044
+ optparse.parse!(args)
2045
+ if args.count != 2
2046
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
2047
+ end
2048
+ connect(options)
2049
+ _remove_container_group(args, options, resource_type)
2050
+ end
2051
+
2052
+ def restart_deployment(args)
2053
+ resource_type = 'deployment'
2054
+ options = {}
2055
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2056
+ opts.banner = subcommand_usage("[cluster] [#{resource_type}]")
2057
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
2058
+ opts.footer = "Restart a #{resource_type} within a cluster.\n" +
2059
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
2060
+ "[#{resource_type}] is required. This is the name or id of an existing #{resource_type}."
2061
+ end
2062
+ optparse.parse!(args)
2063
+ if args.count != 2
2064
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
2065
+ end
2066
+ connect(options)
2067
+ _restart_container_group(args, options, resource_type)
2068
+ end
2069
+
2070
+ def list_stateful_sets(args)
2071
+ resource_type = 'statefulset'
2072
+ options = {}
2073
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2074
+ opts.banner = subcommand_usage( "[cluster]")
2075
+ opts.on("--resource-level LEVEL", String, "Resource Level") do |val|
2076
+ options[:resourceLevel] = val.to_s
2077
+ end
2078
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
2079
+ opts.footer = "List #{resource_type}s for a cluster.\n" +
2080
+ "[cluster] is required. This is the name or id of an existing cluster."
2081
+ end
2082
+ optparse.parse!(args)
2083
+ if args.count != 1
2084
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
2085
+ end
2086
+ connect(options)
2087
+ _list_container_groups(args, options, resource_type)
2088
+ end
2089
+
2090
+ def remove_stateful_set(args)
2091
+ resource_type = 'statefulset'
2092
+ options = {}
2093
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2094
+ opts.banner = subcommand_usage("[cluster] [#{resource_type}]")
2095
+ opts.on( '-f', '--force', "Force Delete" ) do
2096
+ options[:force] = 'on'
2097
+ end
2098
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
2099
+ opts.footer = "Delete a #{resource_type} within a cluster.\n" +
2100
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
2101
+ "[#{resource_type}] is required. This is the name or id of an existing #{resource_type}."
2102
+ end
2103
+ optparse.parse!(args)
2104
+ if args.count != 2
2105
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
2106
+ end
2107
+ connect(options)
2108
+ _remove_container_group(args, options, resource_type)
2109
+ end
2110
+
2111
+ def restart_stateful_set(args)
2112
+ resource_type = 'statefulset'
2113
+ options = {}
2114
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2115
+ opts.banner = subcommand_usage("[cluster] [#{resource_type}]")
2116
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
2117
+ opts.footer = "Restart a #{resource_type} within a cluster.\n" +
2118
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
2119
+ "[#{resource_type}] is required. This is the name or id of an existing #{resource_type}."
2120
+ end
2121
+ optparse.parse!(args)
2122
+ if args.count != 2
2123
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
2124
+ end
2125
+ connect(options)
2126
+ _restart_container_group(args, options, resource_type)
2127
+ end
2128
+
2129
+ def list_pods(args)
2130
+ resource_type = 'pod'
2131
+ options = {}
2132
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2133
+ opts.banner = subcommand_usage( "[cluster]")
2134
+ opts.on("--resource-level LEVEL", String, "Resource Level") do |val|
2135
+ options[:resourceLevel] = val.to_s
2136
+ end
2137
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
2138
+ opts.footer = "List #{resource_type}s for a cluster.\n" +
2139
+ "[cluster] is required. This is the name or id of an existing cluster."
2140
+ end
2141
+ optparse.parse!(args)
2142
+ if args.count != 1
2143
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
2144
+ end
2145
+ connect(options)
2146
+ _list_container_groups(args, options, resource_type)
2147
+ end
2148
+
2149
+ def remove_pod(args)
2150
+ resource_type = 'pod'
2151
+ options = {}
2152
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2153
+ opts.banner = subcommand_usage("[cluster] [#{resource_type}]")
2154
+ opts.on( '-f', '--force', "Force Delete" ) do
2155
+ options[:force] = 'on'
2156
+ end
2157
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
2158
+ opts.footer = "Delete a #{resource_type} within a cluster.\n" +
2159
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
2160
+ "[#{resource_type}] is required. This is the name or id of an existing #{resource_type}."
2161
+ end
2162
+ optparse.parse!(args)
2163
+ if args.count != 2
2164
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
2165
+ end
2166
+ connect(options)
2167
+ _remove_container_group(args, options, resource_type)
2168
+ end
2169
+
2170
+ def restart_pod(args)
2171
+ resource_type = 'pod'
2172
+ options = {}
2173
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2174
+ opts.banner = subcommand_usage("[cluster] [#{resource_type}]")
2175
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
2176
+ opts.footer = "Restart a #{resource_type} within a cluster.\n" +
2177
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
2178
+ "[#{resource_type}] is required. This is the name or id of an existing #{resource_type}."
2179
+ end
2180
+ optparse.parse!(args)
2181
+ if args.count != 2
2182
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
2183
+ end
2184
+ connect(options)
2185
+ _restart_container_group(args, options, resource_type)
2186
+ end
2187
+
2188
+ def add_namespace(args)
2189
+ options = {}
2190
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2191
+ opts.banner = subcommand_usage( "[cluster] [name] [options]")
2192
+ opts.on("--name NAME", String, "Name of the new namespace") do |val|
2193
+ options[:name] = val.to_s
2194
+ end
2195
+ opts.on("--description [TEXT]", String, "Description") do |val|
2196
+ options[:description] = val.to_s
2197
+ end
2198
+ opts.on('--active [on|off]', String, "Enable namespace") do |val|
2199
+ options[:active] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
2200
+ end
2201
+ add_perms_options(opts, options)
2202
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
2203
+ opts.footer = "Create a cluster namespace.\n" +
2204
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
2205
+ "[name] is required. This is the name of the new namespace."
2206
+ end
2207
+
2208
+ optparse.parse!(args)
2209
+ if args.count < 1 || args.count > 3
2210
+ raise_command_error "wrong number of arguments, expected 1 to 3 and got (#{args.count}) #{args}\n#{optparse}"
2211
+ end
2212
+ connect(options)
2213
+
2214
+ begin
2215
+ cluster = find_cluster_by_name_or_id(args[0])
2216
+ return 1 if cluster.nil?
2217
+ payload = nil
2218
+ if options[:payload]
2219
+ payload = options[:payload]
2220
+ # support -O OPTION switch on top of --payload
2221
+ if options[:options]
2222
+ payload['namespace'] ||= {}
2223
+ payload['namespace'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) })
2224
+ end
2225
+ else
2226
+ namespace_payload = {'name' => options[:name] || (args.length > 1 ? args[1] : nil) || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'description' => 'Namespace Name', 'required' => true}], options[:options], @api_client)['name']}
2227
+ namespace_payload.deep_merge!(prompt_update_namespace(options).reject {|k,v| k.is_a?(Symbol)})
2228
+ payload = {"namespace" => namespace_payload}
2229
+ end
2230
+
2231
+ @clusters_interface.setopts(options)
2232
+ if options[:dry_run]
2233
+ print_dry_run @clusters_interface.dry.create_namespace(cluster['id'], payload)
2234
+ return
2235
+ end
2236
+ json_response = @clusters_interface.create_namespace(cluster['id'], payload)
2237
+ if options[:json]
2238
+ puts as_json(json_response, options)
2239
+ elsif !options[:quiet]
2240
+ namespace = json_response['namespace']
2241
+ print_green_success "Added namespace #{namespace['name']}"
2242
+ get_args = [cluster["id"], namespace["id"]] + (options[:remote] ? ["-r",options[:remote]] : [])
2243
+ get_namespace(get_args)
2244
+ end
2245
+ return 0
2246
+ rescue RestClient::Exception => e
2247
+ print_rest_exception(e, options)
2248
+ exit 1
2249
+ end
2250
+ end
2251
+
2252
+ def list_namespaces(args)
2253
+ options = {}
2254
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2255
+ opts.banner = subcommand_usage( "[cluster]")
2256
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
2257
+ opts.footer = "List namespaces for a cluster.\n" +
2258
+ "[cluster] is required. This is the name or id of an existing cluster."
2259
+ end
2260
+
2261
+ optparse.parse!(args)
2262
+ if args.count != 1
2263
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
2264
+ end
2265
+ connect(options)
2266
+ begin
2267
+ cluster = find_cluster_by_name_or_id(args[0])
2268
+ return 1 if cluster.nil?
2269
+
2270
+ params = {}
2271
+ params.merge!(parse_list_options(options))
2272
+ @clusters_interface.setopts(options)
2273
+ if options[:dry_run]
2274
+ print_dry_run @clusters_interface.dry.list_namespaces(cluster['id'], params)
2275
+ return
2276
+ end
2277
+ json_response = @clusters_interface.list_namespaces(cluster['id'], params)
2278
+
2279
+ render_result = render_with_format(json_response, options, 'namespaces')
2280
+ return 0 if render_result
2281
+
2282
+ title = "Morpheus Cluster Namespaces: #{cluster['name']}"
2283
+ subtitles = []
2284
+ subtitles += parse_list_subtitles(options)
2285
+ print_h1 title, subtitles
2286
+ namespaces = json_response['namespaces']
2287
+ if namespaces.empty?
2288
+ print yellow,"No namespaces found.",reset,"\n"
2289
+ else
2290
+ # more stuff to show here
2291
+ rows = namespaces.collect do |ns|
2292
+ {
2293
+ id: ns['id'],
2294
+ name: ns['name'],
2295
+ description: ns['description'],
2296
+ status: ns['status'],
2297
+ active: format_boolean(ns['active']),
2298
+ cluster: cluster['name']
2299
+ }
2300
+ end
2301
+ columns = [
2302
+ :id, :name, :description, :status, :active #, :cluster => lambda { |it| cluster['name'] }
2303
+ ]
2304
+ print as_pretty_table(rows, columns, options)
2305
+ end
2306
+ print reset,"\n"
2307
+ return 0
2308
+ rescue RestClient::Exception => e
2309
+ print_rest_exception(e, options)
2310
+ exit 1
2311
+ end
2312
+ end
2313
+
2314
+ def get_namespace(args)
2315
+ options = {}
2316
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2317
+ opts.banner = subcommand_usage( "[cluster] [namespace]")
2318
+ opts.on( nil, '--permissions', "Display permissions" ) do
2319
+ options[:show_perms] = true
2320
+ end
2321
+ build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
2322
+ opts.footer = "Get details about a cluster namespace.\n" +
2323
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
2324
+ "[namespace] is required. This is the name or id of an existing namespace."
2325
+ end
2326
+ optparse.parse!(args)
2327
+ if args.count != 2
2328
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
2329
+ end
2330
+ connect(options)
2331
+ begin
2332
+ cluster = find_cluster_by_name_or_id(args[0])
2333
+ return 1 if cluster.nil?
2334
+ # this finds the namespace in the cluster api response, then fetches it by ID
2335
+ namespace = find_namespace_by_name_or_id(cluster['id'], args[1])
2336
+ if namespace.nil?
2337
+ print_red_alert "Namespace not found for '#{args[1]}'"
2338
+ exit 1
2339
+ end
2340
+ params = {}
2341
+ params.merge!(parse_list_options(options))
2342
+ @clusters_interface.setopts(options)
2343
+ if options[:dry_run]
2344
+ print_dry_run @clusters_interface.dry.get_namespace(cluster['id'], namespace['id'], params)
2345
+ return
2346
+ end
2347
+ json_response = @clusters_interface.get_namespace(cluster['id'], namespace['id'], params)
2348
+
2349
+ render_result = render_with_format(json_response, options, 'namespace')
2350
+ return 0 if render_result
2351
+
2352
+ print_h1 "Morpheus Cluster Namespace"
2353
+ print cyan
2354
+ description_cols = {
2355
+ "ID" => 'id',
2356
+ "Name" => 'name',
2357
+ "Description" => 'description',
2358
+ "Cluster" => lambda { |it| cluster['name'] },
2359
+ "Status" => 'status',
2360
+ "Active" => lambda {|it| format_boolean it['active'] }
2361
+ # more stuff to show here
2362
+ }
2363
+ print_description_list(description_cols, namespace)
2364
+ print reset,"\n"
2365
+
2366
+ if options[:show_perms]
2367
+ permissions = cluster['permissions']
2368
+ print_permissions(permissions)
2369
+ end
2370
+
2371
+ return 0
2372
+ rescue RestClient::Exception => e
2373
+ print_rest_exception(e, options)
2374
+ exit 1
2375
+ end
2376
+ end
2377
+
2378
+ def update_namespace(args)
2379
+ options = {}
2380
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2381
+ opts.banner = subcommand_usage( "[cluster] [namespace] [options]")
2382
+ opts.on("--description [TEXT]", String, "Description") do |val|
2383
+ options[:description] = val.to_s
2384
+ end
2385
+ opts.on('--active [on|off]', String, "Enable namespace") do |val|
2386
+ options[:active] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
2387
+ end
2388
+ add_perms_options(opts, options)
2389
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
2390
+ opts.footer = "Update a cluster namespace.\n" +
2391
+ "[cluster] is required. This is the name or id of an existing cluster.\n" +
2392
+ "[namespace] is required. This is the name or id of an existing namespace."
2393
+ end
2394
+
2395
+ optparse.parse!(args)
2396
+ if args.count != 2
2397
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
2398
+ end
2399
+ connect(options)
2400
+
2401
+ begin
2402
+ cluster = find_cluster_by_name_or_id(args[0])
2403
+ return 1 if cluster.nil?
2404
+ namespace = find_namespace_by_name_or_id(cluster['id'], args[1])
2405
+ if namespace.nil?
2406
+ print_red_alert "Namespace not found by '#{args[1]}'"
2407
+ exit 1
2408
+ end
2409
+ payload = nil
2410
+ if options[:payload]
2411
+ payload = options[:payload]
2412
+ # support -O OPTION switch on top of everything
2413
+ if options[:options]
2414
+ payload.deep_merge!({'namespace' => options[:options].reject {|k,v| k.is_a?(Symbol) }})
2415
+ end
2416
+ else
2417
+ payload = {'namespace' => prompt_update_namespace(options)}
2418
+
2419
+ # support -O OPTION switch on top of everything
2420
+ if options[:options]
2421
+ payload.deep_merge!({'namespace' => options[:options].reject {|k,v| k.is_a?(Symbol) }})
2422
+ end
2423
+
2424
+ if payload['namespace'].nil? || payload['namespace'].empty?
2425
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
2426
+ end
2427
+ end
2428
+
2429
+ @clusters_interface.setopts(options)
2430
+ if options[:dry_run]
2431
+ print_dry_run @clusters_interface.dry.update_namespace(cluster['id'], namespace['id'], payload)
2432
+ return
2433
+ end
2434
+ json_response = @clusters_interface.update_namespace(cluster['id'], namespace['id'], payload)
2435
+ if options[:json]
2436
+ puts as_json(json_response)
2437
+ elsif !options[:quiet]
2438
+ namespace = json_response['namespace']
2439
+ print_green_success "Updated namespace #{namespace['name']}"
2440
+ get_args = [cluster["id"], namespace["id"]] + (options[:remote] ? ["-r",options[:remote]] : [])
2441
+ get_namespace(get_args)
2442
+ end
2443
+ return 0
2444
+ rescue RestClient::Exception => e
2445
+ print_rest_exception(e, options)
2446
+ exit 1
2447
+ end
2448
+ end
2449
+
2450
+ def remove_namespace(args)
2451
+ options = {}
2452
+ query_params = {}
2453
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2454
+ opts.banner = subcommand_usage("[cluster] [namespace]")
2455
+ opts.on( '-f', '--force', "Force Delete" ) do
2456
+ query_params[:force] = 'on'
2457
+ end
2458
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
2459
+ opts.footer = "Delete a namespace within a cluster."
2460
+ end
2461
+ optparse.parse!(args)
2462
+ if args.count != 2
2463
+ raise_command_error "wrong number of arguments, expected 2 and got (#{args.count}) #{args}\n#{optparse}"
2464
+ end
2465
+ connect(options)
2466
+
2467
+ begin
2468
+ cluster = find_cluster_by_name_or_id(args[0])
2469
+ return 1 if cluster.nil?
2470
+ namespace = find_namespace_by_name_or_id(cluster['id'], args[1])
2471
+ if namespace.nil?
2472
+ print_red_alert "Namespace not found by '#{args[1]}'"
2473
+ exit 1
2474
+ end
2475
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to remove the cluster namespace '#{namespace['name']}'?", options)
2476
+ return 9, "aborted command"
2477
+ end
2478
+ @clusters_interface.setopts(options)
2479
+ if options[:dry_run]
2480
+ print_dry_run @clusters_interface.dry.destroy_namespace(cluster['id'], namespace['id'], query_params)
2481
+ return
2482
+ end
2483
+ json_response = @clusters_interface.destroy_namespace(cluster['id'], namespace['id'], query_params)
2484
+ if options[:json]
2485
+ print JSON.pretty_generate(json_response)
2486
+ print "\n"
2487
+ elsif !options[:quiet]
2488
+ print_green_success "Removed cluster namespace #{namespace['name']}"
2489
+ #list([])
2490
+ end
2491
+ rescue RestClient::Exception => e
2492
+ print_rest_exception(e, options)
2493
+ exit 1
2494
+ end
2495
+ end
2496
+
2497
+ def api_config(args)
2498
+ options = {}
2499
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2500
+ opts.banner = subcommand_usage("[cluster]")
2501
+ build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
2502
+ opts.footer = "Display API service settings for a cluster."
2503
+ end
2504
+ optparse.parse!(args)
2505
+ if args.count != 1
2506
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
2507
+ end
2508
+ connect(options)
2509
+ begin
2510
+ cluster = find_cluster_by_name_or_id(args[0])
2511
+ return 1 if cluster.nil?
2512
+ params = {}
2513
+ params.merge!(parse_list_options(options))
2514
+ @clusters_interface.setopts(options)
2515
+ if options[:dry_run]
2516
+ print_dry_run @clusters_interface.dry.api_config(cluster['id'], params)
2517
+ return
2518
+ end
2519
+ json_response = @clusters_interface.api_config(cluster['id'], params)
2520
+
2521
+ render_result = render_with_format(json_response, options)
2522
+ return 0 if render_result
2523
+
2524
+ title = "Cluster API Config: #{cluster['name']}"
2525
+ subtitles = []
2526
+ subtitles += parse_list_subtitles(options)
2527
+ print_h1 title, subtitles, options
2528
+
2529
+ service_config = json_response
2530
+ print cyan
2531
+ description_cols = {
2532
+ "Url" => 'serviceUrl',
2533
+ "Username" => 'serviceUsername',
2534
+ #"Password" => 'servicePassword',
2535
+ "Token" => 'serviceToken',
2536
+ "Access" => 'serviceAccess',
2537
+ "Cert" => 'serviceCert',
2538
+ #"Config" => 'serviceConfig',
2539
+ "Version" => 'serviceVersion',
2540
+ }
2541
+ print_description_list(description_cols, service_config)
2542
+ print reset,"\n"
2543
+ return 0
2544
+
2545
+ rescue RestClient::Exception => e
2546
+ print_rest_exception(e, options)
2547
+ exit 1
2548
+ end
2549
+ end
2550
+
2551
+ def view_kube_config(args)
2552
+ options = {}
2553
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2554
+ opts.banner = subcommand_usage("[cluster]")
2555
+ build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
2556
+ opts.footer = "Display Kubernetes config for a cluster."
2557
+ end
2558
+ optparse.parse!(args)
2559
+ if args.count != 1
2560
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
2561
+ end
2562
+ connect(options)
2563
+ begin
2564
+ cluster = find_cluster_by_name_or_id(args[0])
2565
+ return 1 if cluster.nil?
2566
+ params = {}
2567
+ params.merge!(parse_list_options(options))
2568
+ @clusters_interface.setopts(options)
2569
+ if options[:dry_run]
2570
+ print_dry_run @clusters_interface.dry.api_config(cluster['id'], params)
2571
+ return
2572
+ end
2573
+ json_response = @clusters_interface.api_config(cluster['id'], params)
2574
+
2575
+ render_result = render_with_format(json_response, options)
2576
+ return 0 if render_result
2577
+
2578
+ title = "Cluster Kube Config: #{cluster['name']}"
2579
+ subtitles = []
2580
+ subtitles += parse_list_subtitles(options)
2581
+ print_h1 title, subtitles, options
2582
+
2583
+ service_config = json_response
2584
+ service_access = service_config['serviceAccess']
2585
+ if service_access.to_s.empty?
2586
+ print yellow,"No kube config found.",reset,"\n\n"
2587
+ return 1
2588
+ else
2589
+ print cyan,service_access,reset,"\n\n"
2590
+ return 0
2591
+ end
2592
+
2593
+ rescue RestClient::Exception => e
2594
+ print_rest_exception(e, options)
2595
+ exit 1
2596
+ end
2597
+ end
2598
+
2599
+ def view_api_token(args)
2600
+ print_token_only = false
2601
+ options = {}
2602
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2603
+ opts.banner = subcommand_usage("[cluster]")
2604
+ build_common_options(opts, options, [:dry_run, :remote])
2605
+ opts.on('-t','--token-only', "Print the api token only") do
2606
+ print_token_only = true
2607
+ end
2608
+ opts.footer = "Display api token for a cluster."
2609
+ end
2610
+ optparse.parse!(args)
2611
+ if args.count != 1
2612
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args}\n#{optparse}"
2613
+ end
2614
+ connect(options)
2615
+ begin
2616
+ cluster = find_cluster_by_name_or_id(args[0])
2617
+ return 1 if cluster.nil?
2618
+ params = {}
2619
+ params.merge!(parse_list_options(options))
2620
+ @clusters_interface.setopts(options)
2621
+ if options[:dry_run]
2622
+ print_dry_run @clusters_interface.dry.api_config(cluster['id'], params)
2623
+ return
2624
+ end
2625
+ json_response = @clusters_interface.api_config(cluster['id'], params)
2626
+
2627
+ render_result = render_with_format(json_response, options)
2628
+ return 0 if render_result
2629
+
2630
+ service_config = json_response
2631
+ service_token = service_config['serviceToken']
2632
+
2633
+ if print_token_only
2634
+ if service_token.to_s.empty?
2635
+ print yellow,"No api token found.",reset,"\n"
2636
+ return 1
2637
+ else
2638
+ print cyan,service_token,reset,"\n"
2639
+ return 0
2640
+ end
2641
+ end
2642
+
2643
+ title = "Cluster API Token: #{cluster['name']}"
2644
+ subtitles = []
2645
+ print_h1 title, subtitles, options
2646
+
2647
+ if service_token.to_s.empty?
2648
+ print yellow,"No api token found.",reset,"\n\n"
2649
+ return 1
2650
+ else
2651
+ print cyan,service_token,reset,"\n\n"
2652
+ return 0
2653
+ end
2654
+ rescue RestClient::Exception => e
2655
+ print_rest_exception(e, options)
2656
+ exit 1
2657
+ end
2658
+ end
2659
+
2660
+ def wiki(args)
2661
+ options = {}
2662
+ params = {}
2663
+ open_wiki_link = false
2664
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2665
+ opts.banner = subcommand_usage("[cluster]")
2666
+ opts.on('--view', '--view', "View wiki page in web browser.") do
2667
+ open_wiki_link = true
2668
+ end
2669
+ build_common_options(opts, options, [:json, :dry_run, :remote])
2670
+ opts.footer = "View wiki page details for a cluster." + "\n" +
2671
+ "[cluster] is required. This is the name or id of a cluster."
2672
+ end
2673
+ optparse.parse!(args)
2674
+ if args.count != 1
2675
+ puts_error "#{Morpheus::Terminal.angry_prompt}wrong number of arguments. Expected 1 and received #{args.count} #{args.inspect}\n#{optparse}"
2676
+ return 1
2677
+ end
2678
+ connect(options)
2679
+
2680
+ begin
2681
+ cluster = find_cluster_by_name_or_id(args[0])
2682
+ return 1 if cluster.nil?
2683
+
2684
+
2685
+ @clusters_interface.setopts(options)
2686
+ if options[:dry_run]
2687
+ print_dry_run @clusters_interface.dry.wiki(cluster["id"], params)
2688
+ return
2689
+ end
2690
+ json_response = @clusters_interface.wiki(cluster["id"], params)
2691
+ page = json_response['page']
2692
+
2693
+ render_result = render_with_format(json_response, options, 'page')
2694
+ return 0 if render_result
2695
+
2696
+ if page
2697
+
2698
+ # my_terminal.exec("wiki get #{page['id']}")
2699
+
2700
+ print_h1 "cluster Wiki Page: #{cluster['name']}"
2701
+ # print_h1 "Wiki Page Details"
2702
+ print cyan
2703
+
2704
+ print_description_list({
2705
+ "Page ID" => 'id',
2706
+ "Name" => 'name',
2707
+ #"Category" => 'category',
2708
+ #"Ref Type" => 'refType',
2709
+ #"Ref ID" => 'refId',
2710
+ #"Owner" => lambda {|it| it['account'] ? it['account']['name'] : '' },
2711
+ "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
2712
+ "Created By" => lambda {|it| it['createdBy'] ? it['createdBy']['username'] : '' },
2713
+ "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
2714
+ "Updated By" => lambda {|it| it['updatedBy'] ? it['updatedBy']['username'] : '' }
2715
+ }, page)
2716
+ print reset,"\n"
2717
+
2718
+ print_h2 "Page Content"
2719
+ print cyan, page['content'], reset, "\n"
2720
+
2721
+ else
2722
+ print "\n"
2723
+ print cyan, "No wiki page found.", reset, "\n"
2724
+ end
2725
+ print reset,"\n"
2726
+
2727
+ if open_wiki_link
2728
+ return view_wiki([args[0]])
2729
+ end
2730
+
2731
+ return 0
2732
+ rescue RestClient::Exception => e
2733
+ print_rest_exception(e, options)
2734
+ exit 1
2735
+ end
2736
+ end
2737
+
2738
+ def view_wiki(args)
2739
+ params = {}
2740
+ options = {}
2741
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2742
+ opts.banner = subcommand_usage("[id]")
2743
+ build_common_options(opts, options, [:dry_run, :remote])
2744
+ opts.footer = "View cluster wiki page in a web browser" + "\n" +
2745
+ "[cluster] is required. This is the name or id of a cluster."
2746
+ end
2747
+ optparse.parse!(args)
2748
+ if args.count != 1
2749
+ raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
2750
+ end
2751
+ connect(options)
2752
+ begin
2753
+ cluster = find_cluster_by_name_or_id(args[0])
2754
+ return 1 if cluster.nil?
2755
+
2756
+ link = "#{@appliance_url}/login/oauth-redirect?access_token=#{@access_token}\\&redirectUri=/infrastructure/clusters/#{cluster['id']}#!wiki"
2757
+
2758
+ open_command = nil
2759
+ if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
2760
+ open_command = "start #{link}"
2761
+ elsif RbConfig::CONFIG['host_os'] =~ /darwin/
2762
+ open_command = "open #{link}"
2763
+ elsif RbConfig::CONFIG['host_os'] =~ /linux|bsd/
2764
+ open_command = "xdg-open #{link}"
2765
+ end
2766
+
2767
+ if options[:dry_run]
2768
+ puts "system: #{open_command}"
2769
+ return 0
2770
+ end
2771
+
2772
+ system(open_command)
2773
+
2774
+ return 0
2775
+ rescue RestClient::Exception => e
2776
+ print_rest_exception(e, options)
2777
+ exit 1
2778
+ end
2779
+ end
2780
+
2781
+ def update_wiki(args)
2782
+ options = {}
2783
+ params = {}
2784
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2785
+ opts.banner = subcommand_usage("[cluster] [options]")
2786
+ build_option_type_options(opts, options, update_wiki_page_option_types)
2787
+ opts.on('--file FILE', "File containing the wiki content. This can be used instead of --content") do |filename|
2788
+ full_filename = File.expand_path(filename)
2789
+ if File.exists?(full_filename)
2790
+ params['content'] = File.read(full_filename)
2791
+ else
2792
+ print_red_alert "File not found: #{full_filename}"
2793
+ return 1
2794
+ end
2795
+ # use the filename as the name by default.
2796
+ if !params['name']
2797
+ params['name'] = File.basename(full_filename)
2798
+ end
2799
+ end
2800
+ opts.on(nil, '--clear', "Clear current page content") do |val|
2801
+ params['content'] = ""
2802
+ end
2803
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
2804
+ end
2805
+ optparse.parse!(args)
2806
+ if args.count != 1
2807
+ puts_error "#{Morpheus::Terminal.angry_prompt}wrong number of arguments. Expected 1 and received #{args.count} #{args.inspect}\n#{optparse}"
2808
+ return 1
2809
+ end
2810
+ connect(options)
2811
+
2812
+ begin
2813
+ cluster = find_cluster_by_name_or_id(args[0])
2814
+ return 1 if cluster.nil?
2815
+ # construct payload
2816
+ passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) } : {}
2817
+ payload = nil
2818
+ if options[:payload]
2819
+ payload = options[:payload]
2820
+ payload.deep_merge!({'page' => passed_options}) unless passed_options.empty?
2821
+ else
2822
+ payload = {
2823
+ 'page' => {
2824
+ }
2825
+ }
2826
+ # allow arbitrary -O options
2827
+ payload.deep_merge!({'page' => passed_options}) unless passed_options.empty?
2828
+ # prompt for options
2829
+ #params = Morpheus::Cli::OptionTypes.prompt(update_wiki_page_option_types, options[:options], @api_client, options[:params])
2830
+ #params = passed_options
2831
+ params.deep_merge!(passed_options)
2832
+
2833
+ if params.empty?
2834
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
2835
+ end
2836
+
2837
+ payload.deep_merge!({'page' => params}) unless params.empty?
2838
+ end
2839
+ @clusters_interface.setopts(options)
2840
+ if options[:dry_run]
2841
+ print_dry_run @clusters_interface.dry.update_wiki(cluster["id"], payload)
2842
+ return
2843
+ end
2844
+ json_response = @clusters_interface.update_wiki(cluster["id"], payload)
2845
+
2846
+ if options[:json]
2847
+ puts as_json(json_response, options)
2848
+ else
2849
+ print_green_success "Updated wiki page for cluster #{cluster['name']}"
2850
+ wiki([cluster['id']])
2851
+ end
2852
+ return 0
2853
+ rescue RestClient::Exception => e
2854
+ print_rest_exception(e, options)
2855
+ exit 1
2856
+ end
2857
+ end
2858
+
2859
+ def history(args)
2860
+ raw_args = args.dup
2861
+ options = {}
2862
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
2863
+ opts.banner = subcommand_usage("[cluster]")
2864
+ opts.on( nil, '--events', "Display sub processes (events)." ) do
2865
+ options[:show_events] = true
2866
+ end
2867
+ opts.on( nil, '--output', "Display process output." ) do
2868
+ options[:show_output] = true
2869
+ end
2870
+ opts.on('--details', "Display more details: memory and storage usage used / max values." ) do
2871
+ options[:show_events] = true
2872
+ options[:show_output] = true
2873
+ options[:details] = true
2874
+ end
2875
+ opts.on('--process-id ID', String, "Display details about a specfic process only." ) do |val|
2876
+ options[:process_id] = val
2877
+ end
2878
+ opts.on('--event-id ID', String, "Display details about a specfic process event only." ) do |val|
2879
+ options[:event_id] = val
2880
+ end
2881
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
2882
+ opts.footer = "List historical processes for a specific cluster.\n" +
2883
+ "[cluster] is required. This is the name or id of an cluster."
2884
+ end
2885
+
2886
+ optparse.parse!(args)
2887
+
2888
+ # shortcut to other actions
2889
+ if options[:process_id]
2890
+ return history_details(raw_args)
2891
+ elsif options[:event_id]
2892
+ return history_event_details(raw_args)
2893
+ end
2894
+
2895
+ if args.count != 1
2896
+ puts optparse
2897
+ return 1
2898
+ end
2899
+ connect(options)
2900
+ begin
2901
+ cluster = find_cluster_by_name_or_id(args[0])
2902
+ return 1 if cluster.nil?
2903
+ params = {}
2904
+ params.merge!(parse_list_options(options))
2905
+ # params[:query] = params.delete(:phrase) unless params[:phrase].nil?
2906
+ @clusters_interface.setopts(options)
2907
+ if options[:dry_run]
2908
+ print_dry_run @clusters_interface.dry.history(cluster['id'], params)
2909
+ return
2910
+ end
2911
+ json_response = @clusters_interface.history(cluster['id'], params)
2912
+ if options[:json]
2913
+ puts as_json(json_response, options, "processes")
2914
+ return 0
2915
+ elsif options[:yaml]
2916
+ puts as_yaml(json_response, options, "processes")
2917
+ return 0
2918
+ elsif options[:csv]
2919
+ puts records_as_csv(json_response['processes'], options)
2920
+ return 0
2921
+ else
2922
+ title = "Cluster History: #{cluster['name']}"
2923
+ subtitles = []
2924
+ if params[:query]
2925
+ subtitles << "Search: #{params[:query]}".strip
2926
+ end
2927
+ subtitles += parse_list_subtitles(options)
2928
+ print_h1 title, subtitles, options
2929
+ if json_response['processes'].empty?
2930
+ print "#{cyan}No process history found.#{reset}\n\n"
2931
+ else
2932
+ history_records = []
2933
+ json_response["processes"].each do |process|
2934
+ row = {
2935
+ id: process['id'],
2936
+ eventId: nil,
2937
+ uniqueId: process['uniqueId'],
2938
+ name: process['displayName'],
2939
+ description: process['description'],
2940
+ processType: process['processType'] ? (process['processType']['name'] || process['processType']['code']) : process['processTypeName'],
2941
+ createdBy: process['createdBy'] ? (process['createdBy']['displayName'] || process['createdBy']['username']) : '',
2942
+ startDate: format_local_dt(process['startDate']),
2943
+ duration: format_process_duration(process),
2944
+ status: format_process_status(process),
2945
+ error: format_process_error(process, options[:details] ? nil : 20),
2946
+ output: format_process_output(process, options[:details] ? nil : 20)
2947
+ }
2948
+ history_records << row
2949
+ process_events = process['events'] || process['processEvents']
2950
+ if options[:show_events]
2951
+ if process_events
2952
+ process_events.each do |process_event|
2953
+ event_row = {
2954
+ id: process['id'],
2955
+ eventId: process_event['id'],
2956
+ uniqueId: process_event['uniqueId'],
2957
+ name: process_event['displayName'], # blank like the UI
2958
+ description: process_event['description'],
2959
+ processType: process_event['processType'] ? (process_event['processType']['name'] || process_event['processType']['code']) : process['processTypeName'],
2960
+ createdBy: process_event['createdBy'] ? (process_event['createdBy']['displayName'] || process_event['createdBy']['username']) : '',
2961
+ startDate: format_local_dt(process_event['startDate']),
2962
+ duration: format_process_duration(process_event),
2963
+ status: format_process_status(process_event),
2964
+ error: format_process_error(process_event, options[:details] ? nil : 20),
2965
+ output: format_process_output(process_event, options[:details] ? nil : 20)
2966
+ }
2967
+ history_records << event_row
2968
+ end
2969
+ else
2970
+
2971
+ end
2972
+ end
2973
+ end
2974
+ columns = [
2975
+ {:id => {:display_name => "PROCESS ID"} },
2976
+ :name,
2977
+ :description,
2978
+ {:processType => {:display_name => "PROCESS TYPE"} },
2979
+ {:createdBy => {:display_name => "CREATED BY"} },
2980
+ {:startDate => {:display_name => "START DATE"} },
2981
+ {:duration => {:display_name => "ETA/DURATION"} },
2982
+ :status,
2983
+ :error
2984
+ ]
2985
+ if options[:show_events]
2986
+ columns.insert(1, {:eventId => {:display_name => "EVENT ID"} })
2987
+ end
2988
+ if options[:show_output]
2989
+ columns << :output
2990
+ end
2991
+ # custom pretty table columns ...
2992
+ if options[:include_fields]
2993
+ columns = options[:include_fields]
2994
+ end
2995
+ print cyan
2996
+ print as_pretty_table(history_records, columns, options)
2997
+ print_results_pagination(json_response, {:label => "process", :n_label => "processes"})
2998
+ print reset, "\n"
2999
+ return 0
3000
+ end
3001
+ end
3002
+ rescue RestClient::Exception => e
3003
+ print_rest_exception(e, options)
3004
+ exit 1
3005
+ end
3006
+ end
3007
+
3008
+ def history_details(args)
3009
+ options = {}
3010
+ process_id = nil
3011
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
3012
+ opts.banner = subcommand_usage("[cluster] [process-id]")
3013
+ opts.on('--process-id ID', String, "Display details about a specfic event." ) do |val|
3014
+ options[:process_id] = val
3015
+ end
3016
+ opts.add_hidden_option('process-id')
3017
+ build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
3018
+ opts.footer = "Display history details for a specific process.\n" +
3019
+ "[cluster] is required. This is the name or id of a cluster.\n" +
3020
+ "[process-id] is required. This is the id of the process."
3021
+ end
3022
+ optparse.parse!(args)
3023
+ if args.count == 2
3024
+ process_id = args[1]
3025
+ elsif args.count == 1 && options[:process_id]
3026
+ process_id = options[:process_id]
3027
+ else
3028
+ puts_error optparse
3029
+ return 1
3030
+ end
3031
+ connect(options)
3032
+ begin
3033
+ cluster = find_cluster_by_name_or_id(args[0])
3034
+ return 1 if cluster.nil?
3035
+ params = {}
3036
+ params.merge!(parse_list_options(options))
3037
+ params[:query] = params.delete(:phrase) unless params[:phrase].nil?
3038
+ @clusters_interface.setopts(options)
3039
+ if options[:dry_run]
3040
+ print_dry_run @clusters_interface.dry.history_details(cluster['id'], process_id, params)
3041
+ return
3042
+ end
3043
+ json_response = @clusters_interface.history_details(cluster['id'], process_id, params)
3044
+ if options[:json]
3045
+ puts as_json(json_response, options, "process")
3046
+ return 0
3047
+ elsif options[:yaml]
3048
+ puts as_yaml(json_response, options, "process")
3049
+ return 0
3050
+ elsif options[:csv]
3051
+ puts records_as_csv(json_response['process'], options)
3052
+ return 0
3053
+ else
3054
+ process = json_response["process"]
3055
+ title = "Cluster History Details"
3056
+ subtitles = []
3057
+ subtitles << " Process ID: #{process_id}"
3058
+ subtitles += parse_list_subtitles(options)
3059
+ print_h1 title, subtitles, options
3060
+ print_process_details(process)
3061
+
3062
+ print_h2 "Process Events", options
3063
+ process_events = process['events'] || process['processEvents'] || []
3064
+ history_records = []
3065
+ if process_events.empty?
3066
+ puts "#{cyan}No events found.#{reset}"
3067
+ else
3068
+ process_events.each do |process_event|
3069
+ event_row = {
3070
+ id: process_event['id'],
3071
+ eventId: process_event['id'],
3072
+ uniqueId: process_event['uniqueId'],
3073
+ name: process_event['displayName'], # blank like the UI
3074
+ description: process_event['description'],
3075
+ processType: process_event['processType'] ? (process_event['processType']['name'] || process_event['processType']['code']) : process['processTypeName'],
3076
+ createdBy: process_event['createdBy'] ? (process_event['createdBy']['displayName'] || process_event['createdBy']['username']) : '',
3077
+ startDate: format_local_dt(process_event['startDate']),
3078
+ duration: format_process_duration(process_event),
3079
+ status: format_process_status(process_event),
3080
+ error: format_process_error(process_event),
3081
+ output: format_process_output(process_event)
3082
+ }
3083
+ history_records << event_row
3084
+ end
3085
+ columns = [
3086
+ {:id => {:display_name => "EVENT ID"} },
3087
+ :name,
3088
+ :description,
3089
+ {:processType => {:display_name => "PROCESS TYPE"} },
3090
+ {:createdBy => {:display_name => "CREATED BY"} },
3091
+ {:startDate => {:display_name => "START DATE"} },
3092
+ {:duration => {:display_name => "ETA/DURATION"} },
3093
+ :status,
3094
+ :error,
3095
+ :output
3096
+ ]
3097
+ print cyan
3098
+ print as_pretty_table(history_records, columns, options)
3099
+ print_results_pagination({size: process_events.size, total: process_events.size})
3100
+ print reset, "\n"
3101
+ return 0
3102
+ end
3103
+ end
3104
+ rescue RestClient::Exception => e
3105
+ print_rest_exception(e, options)
3106
+ exit 1
3107
+ end
3108
+ end
3109
+
3110
+ def history_event_details(args)
3111
+ options = {}
3112
+ process_event_id = nil
3113
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
3114
+ opts.banner = subcommand_usage("[cluster] [event-id]")
3115
+ opts.on('--event-id ID', String, "Display details about a specfic event." ) do |val|
3116
+ options[:event_id] = val
3117
+ end
3118
+ opts.add_hidden_option('event-id')
3119
+ build_common_options(opts, options, [:query, :json, :yaml, :csv, :fields, :dry_run, :remote])
3120
+ opts.footer = "Display history details for a specific process event.\n" +
3121
+ "[cluster] is required. This is the name or id of an cluster.\n" +
3122
+ "[event-id] is required. This is the id of the process event."
3123
+ end
3124
+ optparse.parse!(args)
3125
+ if args.count == 2
3126
+ process_event_id = args[1]
3127
+ elsif args.count == 1 && options[:event_id]
3128
+ process_event_id = options[:event_id]
3129
+ else
3130
+ puts_error optparse
3131
+ return 1
3132
+ end
3133
+ connect(options)
3134
+ begin
3135
+ cluster = find_cluster_by_name_or_id(args[0])
3136
+ return 1 if cluster.nil?
3137
+ params = {}
3138
+ params.merge!(parse_list_options(options))
3139
+ @clusters_interface.setopts(options)
3140
+ if options[:dry_run]
3141
+ print_dry_run @clusters_interface.dry.history_event_details(cluster['id'], process_event_id, params)
3142
+ return
3143
+ end
3144
+ json_response = @clusters_interface.history_event_details(cluster['id'], process_event_id, params)
3145
+ if options[:json]
3146
+ puts as_json(json_response, options, "processEvent")
3147
+ return 0
3148
+ elsif options[:yaml]
3149
+ puts as_yaml(json_response, options, "processEvent")
3150
+ return 0
3151
+ elsif options[:csv]
3152
+ puts records_as_csv(json_response['processEvent'], options)
3153
+ return 0
3154
+ else
3155
+ process_event = json_response['processEvent'] || json_response['event']
3156
+ title = "Cluster History Event"
3157
+ subtitles = []
3158
+ subtitles += parse_list_subtitles(options)
3159
+ print_h1 title, subtitles, options
3160
+ print_process_event_details(process_event)
3161
+ print reset, "\n"
3162
+ return 0
3163
+ end
3164
+ rescue RestClient::Exception => e
3165
+ print_rest_exception(e, options)
3166
+ exit 1
3167
+ end
3168
+ end
3169
+
3170
+ private
3171
+
3172
+ def print_process_event_details(process_event, options={})
3173
+ # process_event =~ process
3174
+ description_cols = {
3175
+ "Process ID" => lambda {|it| it['processId'] },
3176
+ "Event ID" => lambda {|it| it['id'] },
3177
+ "Name" => lambda {|it| it['displayName'] },
3178
+ "Description" => lambda {|it| it['description'] },
3179
+ "Process Type" => lambda {|it| it['processType'] ? (it['processType']['name'] || it['processType']['code']) : it['processTypeName'] },
3180
+ "Created By" => lambda {|it| it['createdBy'] ? (it['createdBy']['displayName'] || it['createdBy']['username']) : '' },
3181
+ "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
3182
+ "End Date" => lambda {|it| format_local_dt(it['endDate']) },
3183
+ "Duration" => lambda {|it| format_process_duration(it) },
3184
+ "Status" => lambda {|it| format_process_status(it) },
3185
+ }
3186
+ print_description_list(description_cols, process_event)
3187
+
3188
+ if process_event['error']
3189
+ print_h2 "Error", options
3190
+ print reset
3191
+ #puts format_process_error(process_event)
3192
+ puts process_event['error'].to_s.strip
3193
+ end
3194
+
3195
+ if process_event['output']
3196
+ print_h2 "Output", options
3197
+ print reset
3198
+ #puts format_process_error(process_event)
3199
+ puts process_event['output'].to_s.strip
3200
+ end
3201
+ end
3202
+
3203
+ def print_clusters_table(clusters, opts={})
3204
+ table_color = opts[:color] || cyan
3205
+ rows = clusters.collect do |cluster|
3206
+ {
3207
+ id: cluster['id'],
3208
+ name: cluster['name'],
3209
+ type: (cluster['type']['name'] rescue ''),
3210
+ layout: (cluster['layout']['name'] rescue ''),
3211
+ workers: cluster['workerCount'],
3212
+ cloud: (cluster['zone']['name'] rescue ''),
3213
+ status: format_cluster_status(cluster)
3214
+ }
3215
+ end
3216
+ columns = [
3217
+ :id, :name, :type, :layout, :workers, :cloud, :status
3218
+ ]
3219
+ print as_pretty_table(rows, columns, opts)
3220
+ end
3221
+
3222
+ def format_cluster_status(cluster, return_color=cyan)
3223
+ out = ""
3224
+ status_string = cluster['status']
3225
+ if cluster['enabled'] == false
3226
+ out << "#{red}DISABLED#{cluster['statusMessage'] ? "#{return_color} - #{cluster['statusMessage']}" : ''}#{return_color}"
3227
+ elsif status_string.nil? || status_string.empty? || status_string == "unknown"
3228
+ out << "#{white}UNKNOWN#{cluster['statusMessage'] ? "#{return_color} - #{cluster['statusMessage']}" : ''}#{return_color}"
3229
+ elsif status_string == 'ok'
3230
+ out << "#{green}#{status_string.upcase}#{return_color}"
3231
+ elsif status_string == 'syncing' || status_string == 'removing' || status_string.include?('provision')
3232
+ out << "#{yellow}#{status_string.upcase}#{return_color}"
3233
+ else
3234
+ out << "#{red}#{status_string ? status_string.upcase : 'N/A'}#{cluster['statusMessage'] ? "#{return_color} - #{cluster['statusMessage']}" : ''}#{return_color}"
3235
+ end
3236
+ out
3237
+ end
3238
+
3239
+ def format_server_power_state(server, return_color=cyan)
3240
+ out = ""
3241
+ if server['powerState'] == 'on'
3242
+ out << "#{green}ON#{return_color}"
3243
+ elsif server['powerState'] == 'off'
3244
+ out << "#{red}OFF#{return_color}"
3245
+ else
3246
+ out << "#{white}#{server['powerState'].to_s.upcase}#{return_color}"
3247
+ end
3248
+ out
3249
+ end
3250
+
3251
+ def format_server_status(server, return_color=cyan)
3252
+ out = ""
3253
+ status_string = server['status']
3254
+ # todo: colorize, upcase?
3255
+ out << status_string.to_s
3256
+ out
3257
+ end
3258
+
3259
+ def find_cluster_by_name_or_id(val)
3260
+ if val.to_s =~ /\A\d{1,}\Z/
3261
+ find_cluster_by_id(val)
3262
+ else
3263
+ find_cluster_by_name(val)
3264
+ end
3265
+ end
3266
+
3267
+ def find_cluster_by_id(id)
3268
+ json_results = @clusters_interface.get(id.to_i)
3269
+ if json_results['cluster'].empty?
3270
+ print_red_alert "Cluster not found by id #{id}"
3271
+ exit 1
3272
+ end
3273
+ json_results['cluster']
3274
+ end
3275
+
3276
+ def find_cluster_by_name(name)
3277
+ json_results = @clusters_interface.list({name: name})
3278
+ if json_results['clusters'].empty? || json_results['clusters'].count > 1
3279
+ print_red_alert "Cluster not found by name #{name}"
3280
+ exit 1
3281
+ end
3282
+ json_results['clusters'][0]
3283
+ end
3284
+
3285
+ def find_container_by_name_or_id(cluster_id, val)
3286
+ if val.to_s =~ /\A\d{1,}\Z/
3287
+ params = {"containerId": val.to_i}
3288
+ else
3289
+ params = {phrase: val}
3290
+ end
3291
+ json_results = @clusters_interface.list_containers(cluster_id, params)
3292
+ json_results["containers"].empty? ? nil : json_results["containers"][0]
3293
+ end
3294
+
3295
+ def find_container_group_by_name_or_id(cluster_id, resource_type, val)
3296
+ if val.to_s =~ /\A\d{1,}\Z/
3297
+ params = {"#{resource_type}Id": val.to_i}
3298
+ else
3299
+ params = {phrase: val}
3300
+ end
3301
+ json_results = @clusters_interface.list_container_groups(cluster_id, resource_type, params)
3302
+ json_results["#{resource_type}s"].empty? ? nil : json_results["#{resource_type}s"][0]
3303
+ end
3304
+
3305
+ def find_volume_by_name_or_id(cluster_id, val)
3306
+ if val.to_s =~ /\A\d{1,}\Z/
3307
+ params = {volumeId: val.to_i}
3308
+ else
3309
+ params = {phrase: val}
3310
+ end
3311
+ json_results = @clusters_interface.list_volumes(cluster_id, params)
3312
+ json_results['volumes'].empty? ? nil : json_results['volumes'][0]
3313
+ end
3314
+
3315
+ def find_service_by_name_or_id(cluster_id, val)
3316
+ if val.to_s =~ /\A\d{1,}\Z/
3317
+ params = {serviceId: val.to_i}
3318
+ else
3319
+ params = {phrase: val}
3320
+ end
3321
+ json_results = @clusters_interface.list_services(cluster_id, params)
3322
+ json_results['services'].empty? ? nil : json_results['services'][0]
3323
+ end
3324
+
3325
+ def find_namespace_by_name_or_id(cluster_id, val)
3326
+ if val.to_s =~ /\A\d{1,}\Z/
3327
+ params = {namespaceId: val.to_i}
3328
+ else
3329
+ params = {phrase: val}
3330
+ end
3331
+ json_results = @clusters_interface.list_namespaces(cluster_id, params)
3332
+ json_results['namespaces'].empty? ? nil : json_results['namespaces'][0]
3333
+ end
3334
+
3335
+ def find_job_by_name_or_id(cluster_id, val)
3336
+ if val.to_s =~ /\A\d{1,}\Z/
3337
+ params = {jobId: val.to_i}
3338
+ else
3339
+ params = {phrase: val}
3340
+ end
3341
+ json_results = @clusters_interface.list_jobs(cluster_id, params)
3342
+ json_results['jobs'].empty? ? nil : json_results['jobs'][0]
3343
+ end
3344
+
3345
+ def find_cluster_type_by_name_or_id(val)
3346
+ (val.to_s =~ /\A\d{1,}\Z/) ? find_cluster_type_by_id(val) : find_cluster_type_by_name(val)
3347
+ end
3348
+
3349
+ def find_cluster_type_by_id(id)
3350
+ get_cluster_types.find { |it| it['id'] == id }
3351
+ end
3352
+
3353
+ def find_cluster_type_by_name(name)
3354
+ get_cluster_types.find { |it| it['name'].downcase == name.downcase || it['code'].downcase == name.downcase }
3355
+ end
3356
+
3357
+ def cluster_types_for_dropdown
3358
+ get_cluster_types.collect {|it| {'id' => it['id'], 'name' => it['name'], 'code' => it['code'], 'value' => it['code']} }
3359
+ end
3360
+
3361
+ def get_cluster_types(refresh=false)
3362
+ if !@cluster_types || refresh
3363
+ @cluster_types = @clusters_interface.cluster_types()['clusterTypes']
3364
+ end
3365
+ @cluster_types
3366
+ end
3367
+
3368
+ def find_layout_by_name_or_id(val)
3369
+ (val.to_s =~ /\A\d{1,}\Z/) ? find_layout_by_id(val) : find_layout_by_name(val)
3370
+ end
3371
+
3372
+ def find_layout_by_id(id)
3373
+ @compute_type_layouts_interface.get(id)['layout'] rescue nil
3374
+ end
3375
+
3376
+ def find_layout_by_name(name)
3377
+ @compute_type_layouts_interface.list({phrase:name}).find { it['name'].downcase == name.downcase || it['code'].downcase == name.downcase }
3378
+ end
3379
+
3380
+ def layouts_for_dropdown(zone_id, group_type_id)
3381
+ @compute_type_layouts_interface.list({zoneId: zone_id, groupTypeId: group_type_id})["layouts"].collect { |it| {'id' => it['id'], 'name' => it['name'], 'value' => it['id'], 'code' => it['code']} }
3382
+ end
3383
+
3384
+ def find_service_plan_by_name_or_id(val)
3385
+ (val.to_s =~ /\A\d{1,}\Z/) ? find_service_plan_by_id(val) : find_service_plan_by_name(val)
3386
+ end
3387
+
3388
+ def find_service_plan_by_id(id)
3389
+ @servers_interface.service_plan(id)['servicePlan'] rescue nil
3390
+ end
3391
+
3392
+ def find_service_plan_by_name(name)
3393
+ @servers_interface.service_plan({phrase: name})['servicePlans'].find { |it| it['name'].downcase == name.downcase || it['code'].downcase == name.downcase } rescue nil
3394
+ end
3395
+
3396
+ def find_security_group_by_name(val)
3397
+ @security_groups_interface.list({phrase: val})['securityGroups'][0] rescue nil
3398
+ end
3399
+
3400
+ def find_cloud_resource_pool_by_name_or_id(cloud_id, val)
3401
+ (val.to_s =~ /\A\d{1,}\Z/) ? find_cloud_resource_pool_by_id(cloud_id, val) : find_cloud_resource_pool_by_name(cloud_id, val)
3402
+ end
3403
+
3404
+ def find_cloud_resource_pool_by_name(cloud_id, name)
3405
+ get_cloud_resource_pools(cloud_id).find { |it| it['name'].downcase == name.downcase } rescue nil
3406
+ end
3407
+
3408
+ def find_cloud_resource_pool_by_id(cloud_id, id)
3409
+ get_cloud_resource_pools(cloud_id).find { |it| it['id'] == id } rescue nil
3410
+ end
3411
+
3412
+ def get_cloud_resource_pools(cloud_id, refresh=false)
3413
+ if !@cloud_resource_pools || refresh
3414
+ @cloud_resource_pools = @cloud_resource_pools_interface.list(cloud_id)['resourcePools']
3415
+ end
3416
+ @cloud_resource_pools
3417
+ end
3418
+
3419
+ def find_server_type_by_name_or_id(val)
3420
+ (val.to_s =~ /\A\d{1,}\Z/) ? find_server_type_by_name(val) : find_server_type_by_id(val)
3421
+ end
3422
+
3423
+ def find_server_type_by_name(val)
3424
+ @server_types_interface.list({name: val})['serverTypes'][0] rescue nil
3425
+ end
3426
+
3427
+ def find_server_type_by_id(val)
3428
+ @server_types_interface.get(val)['serverType']
3429
+ end
3430
+
3431
+ def service_plans_for_dropdown(zone_id, provision_type_id)
3432
+ @servers_interface.service_plans({zoneId: zone_id, provisionTypeId: provision_type_id})['plans'] rescue []
3433
+ end
3434
+
3435
+ def namespace_service_plans
3436
+ @service_plans_interface.list({'provisionable' => 'any', 'provisionTypeId' => @provision_types_interface.list({'code' => 'docker'})['provisionTypes'].first['id']})['servicePlans'] rescue []
3437
+ end
3438
+
3439
+ def get_cloud_type(id)
3440
+ @clouds_interface.cloud_type(id)['zoneType']
3441
+ end
3442
+
3443
+ def get_provision_type_for_zone_type(zone_type_id)
3444
+ @clouds_interface.cloud_type(zone_type_id)['zoneType']['provisionTypes'].first rescue nil
3445
+ end
3446
+
3447
+ def current_user(refresh=false)
3448
+ if !@current_user || refresh
3449
+ load_whoami
3450
+ end
3451
+ @current_user
3452
+ end
3453
+
3454
+ def load_group(options)
3455
+ # Group / Site
3456
+ group_id = nil
3457
+ group = options[:group] ? find_group_by_name_or_id_for_provisioning(options[:group]) : nil
3458
+
3459
+ if group
3460
+ group_id = group["id"]
3461
+ else
3462
+ if @active_group_id
3463
+ group_id = @active_group_id
3464
+ else
3465
+ available_groups = get_available_groups
3466
+
3467
+ if available_groups.empty?
3468
+ print_red_alert "No available groups"
3469
+ exit 1
3470
+ elsif available_groups.count > 1 && !options[:no_prompt]
3471
+ group_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'type' => 'select', 'fieldLabel' => 'Group', 'selectOptions' => available_groups, 'required' => true, 'description' => 'Select Group.'}],options[:options],@api_client,{})['group']
3472
+ else
3473
+ group_id = available_groups.first['id']
3474
+ end
3475
+ end
3476
+ end
3477
+ @groups_interface.get(group_id)['group']
3478
+ end
3479
+
3480
+ def prompt_service_plan(zone_id, provision_type, options)
3481
+ available_service_plans = service_plans_for_dropdown(zone_id, provision_type['id'])
3482
+ if available_service_plans.empty?
3483
+ print_red_alert "Cloud #{zone_id} has no available plans"
3484
+ exit 1
3485
+ end
3486
+ if options[:servicePlan]
3487
+ service_plan = available_service_plans.find {|sp| sp['id'] == options[:servicePlan].to_i || sp['name'] == options[:servicePlan] || sp['code'] == options[:servicePlan] }
3488
+ else
3489
+ if available_service_plans.count > 1 && !options[:no_prompt]
3490
+ service_plan_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'servicePlan', 'type' => 'select', 'fieldLabel' => 'Plan', 'selectOptions' => available_service_plans, 'required' => true, 'description' => 'Select Plan.'}],options[:options],@api_client,{})['servicePlan'].to_i
3491
+ else
3492
+ service_plan_id = available_service_plans.first['id']
3493
+ end
3494
+ #service_plan = find_service_plan_by_id(service_plan_id)
3495
+ service_plan = available_service_plans.find {|sp| sp['id'] == service_plan_id.to_i || sp['name'] == service_plan_id.to_s || sp['code'] == service_plan_id.to_s }
3496
+ end
3497
+ service_plan
3498
+ end
3499
+
3500
+ def prompt_service_plan_options(service_plan, options)
3501
+ plan_options = {}
3502
+
3503
+ # custom max memory
3504
+ if service_plan['customMaxMemory']
3505
+ if !options[:maxMemory]
3506
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'maxMemory', 'type' => 'number', 'fieldLabel' => 'Max Memory (MB)', 'required' => false, 'description' => 'This will override any memory requirement set on the virtual image', 'defaultValue' => service_plan['maxMemory'] ? service_plan['maxMemory'] / (1024 * 1024) : 10 }], options[:options])
3507
+ plan_options['maxMemory'] = v_prompt['maxMemory'] * 1024 * 1024 if v_prompt['maxMemory']
3508
+ else
3509
+ plan_options['maxMemory'] = options[:maxMemory]
3510
+ end
3511
+ end
3512
+
3513
+ # custom cores: max cpu, max cores, cores per socket
3514
+ if service_plan['customCores']
3515
+ if options[:cpuCount].empty?
3516
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cpuCount', 'type' => 'number', 'fieldLabel' => 'CPU Count', 'required' => false, 'description' => 'Set CPU Count', 'defaultValue' => service_plan['maxCpu'] ? service_plan['maxCpu'] : 1 }], options[:options])
3517
+ plan_options['cpuCount'] = v_prompt['cpuCount'] if v_prompt['cpuCount']
3518
+ else
3519
+ plan_options['cpuCount']
3520
+ end
3521
+ if options[:coreCount].empty?
3522
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'coreCount', 'type' => 'number', 'fieldLabel' => 'Core Count', 'required' => false, 'description' => 'Set Core Count', 'defaultValue' => service_plan['maxCores'] ? service_plan['maxCores'] : 1 }], options[:options])
3523
+ plan_options['coreCount'] = v_prompt['coreCount'] if v_prompt['coreCount']
3524
+ end
3525
+ if options[:coresPerSocket].empty? && service_plan['coresPerSocket']
3526
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'coresPerSocket', 'type' => 'number', 'fieldLabel' => 'Cores Per Socket', 'required' => false, 'description' => 'Set Core Per Socket', 'defaultValue' => service_plan['coresPerSocket']}], options[:options])
3527
+ plan_options['coresPerSocket'] = v_prompt['coresPerSocket'] if v_prompt['coresPerSocket']
3528
+ end
3529
+ end
3530
+ plan_options
3531
+ end
3532
+
3533
+ def add_server_options(opts, options)
3534
+ opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID" ) do |val|
3535
+ options[:cloud] = val
3536
+ end
3537
+ opts.on( '--resource-pool ID', String, "ID of the Resource Pool for Amazon VPC and Azure Resource Group" ) do |val|
3538
+ options[:resourcePool] = val
3539
+ end
3540
+ opts.on( '-p', '--plan PLAN', "Service Plan") do |val|
3541
+ options[:servicePlan] = val
3542
+ end
3543
+ opts.on('--max-memory VALUE', String, "Maximum Memory (MB)") do |val|
3544
+ options[:maxMemory] = val
3545
+ end
3546
+ opts.on('--cpu-count VALUE', String, "CPU Count") do |val|
3547
+ options[:cpuCount] = val
3548
+ end
3549
+ opts.on('--core-count VALUE', String, "Core Count") do |val|
3550
+ options[:coreCount] = val
3551
+ end
3552
+ opts.on('--cores-per-socket VALUE', String, "Cores Per Socket") do |val|
3553
+ options[:coresPerSocket] = val
3554
+ end
3555
+ opts.on('--volumes JSON', String, "Volumes Config JSON") do |val|
3556
+ begin
3557
+ volumes = JSON.parse(val.to_s)
3558
+ rescue JSON::ParserError => e
3559
+ print_red_alert "Unable to parse volumes JSON"
3560
+ exit 1
3561
+ end
3562
+ options[:volumes] = volumes.kind_of?(Array) ? volumes : [volumes]
3563
+ end
3564
+ opts.on('--volumes-file FILE', String, "Volumes Config from a local JSON or YAML file") do |val|
3565
+ config_file = File.expand_path(val)
3566
+ if !File.exists?(config_file) || !File.file?(config_file)
3567
+ print_red_alert "Specified volumes file not found: #{config_file}"
3568
+ exit 1
3569
+ end
3570
+ if config_file =~ /\.ya?ml\Z/
3571
+ begin
3572
+ volumes = YAML.load_file(config_file)
3573
+ rescue YAML::ParseError
3574
+ print_red_alert "Unable to parse volumes YAML from: #{config_file}"
3575
+ exit 1
3576
+ end
3577
+ else
3578
+ volumes =
3579
+ begin
3580
+ volumes = JSON.parse(File.read(config_file))
3581
+ rescue JSON::ParserError
3582
+ print_red_alert "Unable to parse volumes JSON from: #{config_file}"
3583
+ exit 1
3584
+ end
3585
+ end
3586
+ options[:volumes] = volumes.kind_of?(Array) ? volumes : [volumes]
3587
+ end
3588
+ opts.on('--config-file FILE', String, "Instance Config from a local JSON or YAML file") do |val|
3589
+ options['configFile'] = val.to_s
3590
+ end
3591
+ opts.on('--network-interfaces JSON', String, "Network Interfaces Config JSON") do |val|
3592
+ begin
3593
+ networkInterfaces = JSON.parse(val.to_s)
3594
+ rescue JSON::ParserError => e
3595
+ print_red_alert "Unable to parse network interfaces JSON"
3596
+ exit 1
3597
+ end
3598
+ options[:networkInterfaces] = networkInterfaces.kind_of?(Array) ? networkInterfaces : [networkInterfaces]
3599
+ end
3600
+ opts.on('--network-interfaces-file FILE', String, "Network Interfaces Config from a local JSON or YAML file") do |val|
3601
+ config_file = File.expand_path(val)
3602
+ if !File.exists?(config_file) || !File.file?(config_file)
3603
+ print_red_alert "Specified network interfaces file not found: #{config_file}"
3604
+ exit 1
3605
+ end
3606
+ if config_file =~ /\.ya?ml\Z/
3607
+ begin
3608
+ networkInterfaces = YAML.load_file(config_file)
3609
+ rescue YAML::ParseError
3610
+ print_red_alert "Unable to parse network interfaces YAML from: #{config_file}"
3611
+ exit 1
3612
+ end
3613
+ else
3614
+ networkInterfaces =
3615
+ begin
3616
+ networkInterfaces = JSON.parse(File.read(config_file))
3617
+ rescue JSON::ParserError
3618
+ print_red_alert "Unable to parse network interfaces JSON from: #{config_file}"
3619
+ exit 1
3620
+ end
3621
+ end
3622
+ options[:networkInterfaces] = networkInterfaces.kind_of?(Array) ? networkInterfaces : [networkInterfaces]
3623
+ end
3624
+ opts.on('--security-groups LIST', Array, "Security Groups") do |list|
3625
+ options[:securityGroups] = list
3626
+ end
3627
+ opts.on("--create-user on|off", String, "User Config: Create Your User. Default is off") do |val|
3628
+ options[:createUser] = ['true','on','1'].include?(val.to_s)
3629
+ end
3630
+ opts.on("--user-group USERGROUP", String, "User Config: User Group") do |val|
3631
+ options[:userGroup] = val
3632
+ end
3633
+ opts.on('--domain VALUE', String, "Network Domain ID") do |val|
3634
+ options[:domain] = val
3635
+ end
3636
+ opts.on('--hostname VALUE', String, "Hostname") do |val|
3637
+ options[:hostname] = val
3638
+ end
3639
+ end
3640
+
3641
+ def add_perms_options(opts, options)
3642
+ opts.on('--group-access-all [on|off]', String, "Toggle Access for all groups.") do |val|
3643
+ options[:groupAccessAll] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
3644
+ end
3645
+ opts.on('--group-access LIST', Array, "Group Access, comma separated list of group IDs.") do |list|
3646
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
3647
+ options[:groupAccessList] = []
3648
+ else
3649
+ options[:groupAccessList] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
3650
+ end
3651
+ end
3652
+ opts.on('--group-defaults LIST', Array, "Group Default Selection, comma separated list of group IDs") do |list|
3653
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
3654
+ options[:groupDefaultsList] = []
3655
+ else
3656
+ options[:groupDefaultsList] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
3657
+ end
3658
+ end
3659
+ opts.on('--plan-access-all [on|off]', String, "Toggle Access for all service plans.") do |val|
3660
+ options[:planAccessAll] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
3661
+ end
3662
+ opts.on('--plan-access LIST', Array, "Service Plan Access, comma separated list of plan IDs.") do |list|
3663
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
3664
+ options[:planAccessList] = []
3665
+ else
3666
+ options[:planAccessList] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
3667
+ end
3668
+ end
3669
+ opts.on('--plan-defaults LIST', Array, "Plan Default Selection, comma separated list of plan IDs") do |list|
3670
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
3671
+ options[:planDefaultsList] = []
3672
+ else
3673
+ options[:planDefaultsList] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
3674
+ end
3675
+ end
3676
+ opts.on('--visibility [private|public]', String, "Visibility") do |val|
3677
+ options[:visibility] = val
3678
+ end
3679
+ opts.on('--tenants LIST', Array, "Tenant Access, comma separated list of account IDs") do |list|
3680
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
3681
+ options[:tenants] = []
3682
+ else
3683
+ options[:tenants] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
3684
+ end
3685
+ end
3686
+ end
3687
+
3688
+ def prompt_resource_pool(group, cloud, service_plan, provision_type, options)
3689
+ resource_pool = nil
3690
+
3691
+ if provision_type && provision_type['hasZonePools']
3692
+ # Resource pool
3693
+ if !['resourcePoolId', 'resourceGroup', 'vpc'].find { |it| cloud['config'][it] && cloud['config'][it].length > 0 }
3694
+ resource_pool_id = nil
3695
+ resource_pool = options[:resourcePool] ? find_cloud_resource_pool_by_name_or_id(cloud['id'], options[:resourcePool]) : nil
3696
+
3697
+ if !resource_pool
3698
+ resource_pool_options = @options_interface.options_for_source('zonePools', {groupId: group['id'], zoneId: cloud['id'], planId: (service_plan['id'] rescue nil)})['data'].reject { |it| it['id'].nil? && it['name'].nil? }
3699
+
3700
+ if resource_pool_options.empty?
3701
+ print_red_alert "Cloud #{cloud['name']} has no available resource pools"
3702
+ exit 1
3703
+ elsif resource_pool_options.count > 1 && !options[:no_prompt]
3704
+ resource_pool_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'resourcePool', 'type' => 'select', 'fieldLabel' => 'Resource Pool', 'selectOptions' => resource_pool_options, 'required' => true, 'skipSingleOption' => true, 'description' => 'Select resource pool.'}],options[:options],api_client, {})['resourcePool']
3705
+ else
3706
+ resource_pool_id = resource_pool_options.first['id']
3707
+ end
3708
+ resource_pool = @cloud_resource_pools_interface.get(cloud['id'], resource_pool_id)['resourcePool']
3709
+ end
3710
+ end
3711
+ end
3712
+ resource_pool
3713
+ end
3714
+
3715
+ def prompt_security_groups_by_cloud(cloud, provision_type, resource_pool, options)
3716
+ security_groups = options[:securityGroups] || []
3717
+
3718
+ if security_groups.empty? && cloud['zoneType']['hasSecurityGroups'] && ['amazon', 'rds'].include?(provision_type['code']) && resource_pool
3719
+ available_security_groups = @api_client.options.options_for_source('zoneSecurityGroup', {zoneId: cloud['id'], poolId: resource_pool['id']})['data']
3720
+
3721
+ if available_security_groups.empty?
3722
+ #print_red_alert "Cloud #{cloud['name']} has no available plans"
3723
+ #exit 1
3724
+ elsif available_security_groups.count > 1 && !options[:no_prompt]
3725
+ while !available_security_groups.empty? && (security_groups.empty? || Morpheus::Cli::OptionTypes.confirm("Add security group?", {:default => false}))
3726
+ security_group = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'securityGroup', 'type' => 'select', 'fieldLabel' => 'Security Group', 'selectOptions' => available_security_groups, 'required' => true, 'description' => 'Select Security Group.'}], options[:options], @api_client, {})['securityGroup']
3727
+ security_groups << security_group
3728
+ available_security_groups.reject! { |sg| sg['value'] == security_group }
3729
+ end
3730
+ else
3731
+ security_groups << available_security_groups[0]
3732
+ end
3733
+ end
3734
+ security_groups
3735
+ end
3736
+
3737
+ def prompt_update_namespace(options)
3738
+ rtn = {}
3739
+ rtn['description'] = options[:description] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'description' => 'Namespace Description', 'required' => false}], options[:options], @api_client)['description']
3740
+ rtn['active'] = options[:active].nil? ? (Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'active', 'fieldLabel' => 'Active', 'type' => 'checkbox', 'description' => 'Namespace Active', 'defaultValue' => true}], options[:options], @api_client))['active'] == 'on' : options[:active]
3741
+
3742
+ perms = prompt_permissions(options)
3743
+ if perms['resourcePool'] && !perms['resourcePool']['visibility'].nil?
3744
+ rtn['visibility'] = perms['resourcePool']['visibility']
3745
+ perms.delete('resourcePool')
3746
+ end
3747
+ rtn['permissions'] = perms
3748
+ rtn
3749
+ end
3750
+
3751
+ def prompt_permissions(options)
3752
+ all_groups = false
3753
+ group_access = []
3754
+ all_plans = false
3755
+ plan_access = []
3756
+
3757
+ # Group Access
3758
+ if options[:groupAccessAll]
3759
+ all_groups = true
3760
+ end
3761
+
3762
+ if !options[:groupAccessList].empty?
3763
+ group_access = options[:groupAccessList].collect {|site_id| {'id' => site_id.to_i}} || []
3764
+ elsif !options[:no_prompt]
3765
+ available_groups = get_available_groups
3766
+
3767
+ if available_groups.empty?
3768
+ #print_red_alert "No available groups"
3769
+ #exit 1
3770
+ elsif available_groups.count > 1
3771
+ available_groups.unshift({"id" => '0', "name" => "All", "value" => "all"})
3772
+
3773
+ # default to all
3774
+ group_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'type' => 'select', 'fieldLabel' => 'Group Access', 'selectOptions' => available_groups, 'required' => false, 'description' => 'Add Group Access.'}], options[:options], @api_client, {})['group']
3775
+
3776
+ if !group_id.nil?
3777
+ if group_id == 'all'
3778
+ all_groups = true
3779
+ else
3780
+ group_access = [{'id' => group_id, 'default' => Morpheus::Cli::OptionTypes.confirm("Set '#{available_groups.find{|it| it['id'] == group_id}['name']}' as default?", {:default => false})}]
3781
+ end
3782
+ available_groups = available_groups.reject {|it| it['value'] == group_id}
3783
+
3784
+ while !group_id.nil? && !available_groups.empty? && Morpheus::Cli::OptionTypes.confirm("Add another group access?", {:default => false})
3785
+ group_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'type' => 'select', 'fieldLabel' => 'Group Access', 'selectOptions' => available_groups, 'required' => false, 'description' => 'Add Group Access.'}], options[:options], @api_client, {})['group']
3786
+
3787
+ if !group_id.nil?
3788
+ if group_id == 'all'
3789
+ all_groups = true
3790
+ else
3791
+ group_access << {'id' => group_id, 'default' => Morpheus::Cli::OptionTypes.confirm("Set '#{available_groups.find{|it| it['id'] == group_id}['name']}' as default?", {:default => false})}
3792
+ end
3793
+ available_groups = available_groups.reject {|it| it['value'] == group_id}
3794
+ end
3795
+ end
3796
+ end
3797
+ end
3798
+ end
3799
+
3800
+ # Plan Access
3801
+ if options[:planAccessAll]
3802
+ all_plans = true
3803
+ end
3804
+
3805
+ if !options[:planAccessList].empty?
3806
+ plan_access = options[:planAccessList].collect {|plan_id| {'id' => plan_id.to_i}}
3807
+ elsif !options[:no_prompt]
3808
+ available_plans = namespace_service_plans.collect {|it| {'id' => it['id'], 'name' => it['name'], 'value' => it['id']} }
3809
+
3810
+ if available_plans.empty?
3811
+ #print_red_alert "No available plans"
3812
+ #exit 1
3813
+ elsif !available_plans.empty? && !options[:no_prompt]
3814
+ available_plans.unshift({"id" => '0', "name" => "All", "value" => "all"})
3815
+
3816
+ # default to all
3817
+ plan_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'plan', 'type' => 'select', 'fieldLabel' => 'Service Plan Access', 'selectOptions' => available_plans, 'required' => false, 'description' => 'Add Service Plan Access.'}], options[:options], @api_client, {})['plan']
3818
+
3819
+ if !plan_id.nil?
3820
+ if plan_id == 'all'
3821
+ all_plans = true
3822
+ else
3823
+ plan_access = [{'id' => plan_id, 'default' => Morpheus::Cli::OptionTypes.confirm("Set '#{available_plans.find{|it| it['id'] == plan_id}['name']}' as default?", {:default => false})}]
3824
+ end
3825
+
3826
+ available_plans = available_plans.reject {|it| it['value'] == plan_id}
3827
+
3828
+ while !plan_id.nil? && !available_plans.empty? && Morpheus::Cli::OptionTypes.confirm("Add another service plan access?", {:default => false})
3829
+ plan_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'plan', 'type' => 'select', 'fieldLabel' => 'Service Plan Access', 'selectOptions' => available_plans, 'required' => false, 'description' => 'Add Service Plan Access.'}], options[:options], @api_client, {})['plan']
3830
+
3831
+ if !plan_id.nil?
3832
+ if plan_id == 'all'
3833
+ all_plans = true
3834
+ else
3835
+ plan_access << {'id' => plan_id, 'default' => Morpheus::Cli::OptionTypes.confirm("Set '#{available_plans.find{|it| it['id'] == plan_id}['name']}' as default?", {:default => false})}
3836
+ end
3837
+ available_plans = available_plans.reject {|it| it['value'] == plan_id}
3838
+ end
3839
+ end
3840
+ end
3841
+ end
3842
+ end
3843
+
3844
+ resource_perms = {}
3845
+ resource_perms['all'] = true if all_groups
3846
+ resource_perms['sites'] = group_access if !group_access.empty?
3847
+ resource_perms['allPlans'] = true if all_plans
3848
+ resource_perms['plans'] = plan_access if !plan_access.empty?
3849
+
3850
+ permissions = {'resourcePermissions' => resource_perms}
3851
+
3852
+ available_accounts = @accounts_interface.list()['accounts'].collect {|it| {'name' => it['name'], 'value' => it['id']}}
3853
+ accounts = []
3854
+
3855
+ # Prompts for multi tenant
3856
+ if available_accounts.count > 1
3857
+ visibility = options[:visibility] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'visibility', 'fieldLabel' => 'Tenant Permissions Visibility', 'type' => 'select', 'defaultValue' => 'private', 'required' => true, 'selectOptions' => [{'name' => 'Private', 'value' => 'private'},{'name' => 'Public', 'value' => 'public'}]}], options[:options], @api_client, {})['visibility']
3858
+
3859
+ permissions['resourcePool'] = {'visibility' => visibility}
3860
+
3861
+ # Tenants
3862
+ if !options[:tenants].nil?
3863
+ accounts = options[:tenants].collect {|id| id.to_i}
3864
+ elsif !options[:no_prompt]
3865
+ account_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'account_id', 'type' => 'select', 'fieldLabel' => 'Add Tenant', 'selectOptions' => available_accounts, 'required' => false, 'description' => 'Add Tenant Permissions.'}], options[:options], @api_client, {})['account_id']
3866
+
3867
+ if !account_id.nil?
3868
+ accounts << account_id
3869
+ available_accounts = available_accounts.reject {|it| it['value'] == account_id}
3870
+
3871
+ while !available_accounts.empty? && (account_id = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'account_id', 'type' => 'select', 'fieldLabel' => 'Add Another Tenant', 'selectOptions' => available_accounts, 'required' => false, 'description' => 'Add Tenant Permissions.'}], options[:options], @api_client, {})['account_id'])
3872
+ if !account_id.nil?
3873
+ accounts << account_id
3874
+ available_accounts = available_accounts.reject {|it| it['value'] == account_id}
3875
+ end
3876
+ end
3877
+ end
3878
+ end
3879
+ permissions['tenantPermissions'] = {'accounts' => accounts} if !accounts.empty?
3880
+ end
3881
+ permissions
3882
+ end
3883
+
3884
+ def update_wiki_page_option_types
3885
+ [
3886
+ {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => false, 'displayOrder' => 1, 'description' => 'The name of the wiki page for this instance. Default is the instance name.'},
3887
+ #{'fieldName' => 'category', 'fieldLabel' => 'Category', 'type' => 'text', 'required' => false, 'displayOrder' => 2},
3888
+ {'fieldName' => 'content', 'fieldLabel' => 'Content', 'type' => 'textarea', 'required' => false, 'displayOrder' => 3, 'description' => 'The content (markdown) of the wiki page.'}
3889
+ ]
3890
+ end
3891
+
3892
+ def print_permissions(permissions)
3893
+ if permissions.nil?
3894
+ print_h2 "Permissions"
3895
+ print yellow,"No permissions found.",reset,"\n"
3896
+ else
3897
+ if !permissions['resourcePermissions'].nil?
3898
+ print_h2 "Group Access"
3899
+ rows = []
3900
+ if permissions['resourcePermissions']['all']
3901
+ rows.push({group: 'All'})
3902
+ end
3903
+ if permissions['resourcePermissions']['sites']
3904
+ permissions['resourcePermissions']['sites'].each do |site|
3905
+ rows.push({group: site['name'], default: site['default'] ? 'Yes' : ''})
3906
+ end
3907
+ end
3908
+ if rows.empty?
3909
+ print yellow,"No group access",reset,"\n"
3910
+ else
3911
+ columns = [:group, :default]
3912
+ print cyan
3913
+ print as_pretty_table(rows, columns)
3914
+ end
3915
+
3916
+ print_h2 "Plan Access"
3917
+ rows = []
3918
+ if permissions['resourcePermissions']['allPlans']
3919
+ rows.push({plan: 'All'})
3920
+ end
3921
+ if permissions['resourcePermissions']['plans']
3922
+ permissions['resourcePermissions']['plans'].each do |plan|
3923
+ rows.push({plan: plan['name'], default: plan['default'] ? 'Yes' : ''})
3924
+ end
3925
+ end
3926
+ if rows.empty?
3927
+ print yellow,"No plan access",reset,"\n"
3928
+ else
3929
+ columns = [:plan, :default]
3930
+ print cyan
3931
+ print as_pretty_table(rows, columns)
3932
+ end
3933
+
3934
+ if !permissions['tenantPermissions'].nil?
3935
+ print_h2 "Tenant Permissions"
3936
+ if !permissions['resourcePool'].nil?
3937
+ print cyan
3938
+ print "Visibility: #{permissions['resourcePool']['visibility'].to_s.capitalize}".center(20)
3939
+ print "\n"
3940
+ end
3941
+ if !permissions['tenantPermissions'].nil?
3942
+ print cyan
3943
+ print "Accounts: #{permissions['tenantPermissions']['accounts'].join(', ')}".center(20)
3944
+ print "\n"
3945
+ end
3946
+ end
3947
+ print "\n"
3948
+ end
3949
+ end
3950
+ end
3951
+
3952
+ end