morpheus-cli 4.0.0.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
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