morpheus-cli 0.9.10 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/bin/morpheus +48 -33
  3. data/lib/morpheus/api/api_client.rb +12 -0
  4. data/lib/morpheus/api/custom_instance_types.rb +55 -0
  5. data/lib/morpheus/api/custom_instance_types_interface.rb +133 -0
  6. data/lib/morpheus/api/dashboard_interface.rb +37 -0
  7. data/lib/morpheus/api/users_interface.rb +17 -0
  8. data/lib/morpheus/api/whoami_interface.rb +20 -0
  9. data/lib/morpheus/cli.rb +13 -1
  10. data/lib/morpheus/cli/app_templates.rb +1 -1
  11. data/lib/morpheus/cli/cli_command.rb +22 -1
  12. data/lib/morpheus/cli/cli_registry.rb +1 -1
  13. data/lib/morpheus/cli/credentials.rb +33 -11
  14. data/lib/morpheus/cli/dashboard_command.rb +74 -0
  15. data/lib/morpheus/cli/instance_types.rb +4 -2
  16. data/lib/morpheus/cli/key_pairs.rb +1 -1
  17. data/lib/morpheus/cli/library.rb +539 -0
  18. data/lib/morpheus/cli/login.rb +57 -0
  19. data/lib/morpheus/cli/logout.rb +61 -0
  20. data/lib/morpheus/cli/mixins/accounts_helper.rb +52 -10
  21. data/lib/morpheus/cli/mixins/print_helper.rb +23 -16
  22. data/lib/morpheus/cli/mixins/provisioning_helper.rb +1 -1
  23. data/lib/morpheus/cli/mixins/whoami_helper.rb +34 -0
  24. data/lib/morpheus/cli/option_types.rb +15 -1
  25. data/lib/morpheus/cli/recent_activity_command.rb +83 -0
  26. data/lib/morpheus/cli/remote.rb +71 -28
  27. data/lib/morpheus/cli/roles.rb +89 -24
  28. data/lib/morpheus/cli/shell.rb +24 -11
  29. data/lib/morpheus/cli/users.rb +166 -24
  30. data/lib/morpheus/cli/version.rb +1 -1
  31. data/lib/morpheus/cli/version_command.rb +45 -0
  32. data/lib/morpheus/cli/whoami.rb +139 -0
  33. data/lib/morpheus/logging.rb +78 -0
  34. metadata +15 -2
@@ -1,12 +1,13 @@
1
1
  require 'yaml'
2
2
  require 'io/console'
3
3
  require 'rest_client'
4
- require 'term/ansicolor'
5
4
  require 'optparse'
5
+ require 'morpheus/cli/cli_command'
6
6
 
7
7
 
8
8
  class Morpheus::Cli::Remote
9
- include Term::ANSIColor
9
+ include Morpheus::Cli::CliCommand
10
+
10
11
  def initialize()
11
12
  @appliances = ::Morpheus::Cli::Remote.load_appliance_file
12
13
  end
@@ -32,35 +33,56 @@ class Morpheus::Cli::Remote
32
33
  end
33
34
 
34
35
  def list(args)
36
+ options = {}
37
+ optparse = OptionParser.new do|opts|
38
+ opts.banner = "Usage: morpheus remote list"
39
+ build_common_options(opts, options, [])
40
+ end
41
+ optparse.parse(args)
42
+
35
43
  print "\n" ,cyan, bold, "Morpheus Appliances\n","==================", reset, "\n\n"
36
44
  # print red, bold, "red bold", reset, "\n"
37
45
  if @appliances == nil || @appliances.empty?
46
+ puts yellow,"No remote appliances configured.",reset
38
47
  else
48
+ rows = @appliances.collect do |app_name, v|
49
+ {
50
+ active: (v[:active] ? "=>" : ""),
51
+ name: app_name,
52
+ host: v[:host]
53
+ }
54
+ end
55
+ print cyan
56
+ tp rows, {:active => {:display_name => ""}}, {:name => {:width => 16}}, {:host => {:width => 40}}
57
+ print reset
58
+
59
+ # @appliances.each do |app_name, v|
60
+ # print cyan
61
+ # if v[:active] == true
62
+ # print bold, "=> #{app_name}\t#{v[:host]}",reset,"\n"
63
+ # else
64
+ # print "= #{app_name}\t#{v[:host]}",reset,"\n"
65
+ # end
66
+ # end
67
+
68
+ print "\n\n# => - current\n\n"
39
69
  end
40
- @appliances.each do |app_name, v|
41
- print cyan
42
- if v[:active] == true
43
- print bold, "=> #{app_name}\t#{v[:host]}",reset,"\n"
44
- else
45
- print "= #{app_name}\t#{v[:host]}",reset,"\n"
46
- end
47
- end
48
- print "\n\n# => - current\n\n"
49
70
  end
50
71
 
51
72
  def add(args)
52
- if args.count < 2
53
- puts "\nUsage: morpheus remote add [name] [host] [--default]\n\n"
54
- return
55
- end
56
- params = {}
73
+ options = {}
57
74
  optparse = OptionParser.new do|opts|
58
- params[:default] = false
59
- opts.on( '-d', '--default', "Default has been set" ) do
60
- params[:default] = true
75
+ opts.banner = "Usage: morpheus remote add [name] [host] [--default]"
76
+ build_common_options(opts, options, [])
77
+ opts.on( '-d', '--default', "Make this the default remote appliance" ) do
78
+ options[:default] = true
61
79
  end
62
80
  end
63
81
  optparse.parse(args)
82
+ if args.count < 2
83
+ puts "\n#{optparse.banner}\n\n"
84
+ return
85
+ end
64
86
 
65
87
  name = args[0].to_sym
66
88
  if @appliances[name] != nil
@@ -70,7 +92,7 @@ class Morpheus::Cli::Remote
70
92
  host: args[1],
71
93
  active: false
72
94
  }
73
- if params[:default] == true
95
+ if options[:default] == true
74
96
  set_active_appliance name
75
97
  end
76
98
  end
@@ -79,10 +101,20 @@ class Morpheus::Cli::Remote
79
101
  end
80
102
 
81
103
  def remove(args)
104
+ options = {}
105
+ optparse = OptionParser.new do|opts|
106
+ opts.banner = "Usage: morpheus remote remove [name]"
107
+ build_common_options(opts, options, [])
108
+ opts.on( '-d', '--default', "Make this the default remote appliance" ) do
109
+ options[:default] = true
110
+ end
111
+ end
112
+ optparse.parse(args)
82
113
  if args.empty?
83
- puts "\nUsage: morpheus remote remove [name]\n\n"
114
+ puts "\n#{optparse.banner}\n\n"
84
115
  return
85
116
  end
117
+
86
118
  name = args[0].to_sym
87
119
  if @appliances[name] == nil
88
120
  print red, "Remote appliance not configured for #{args[0]}", reset, "\n"
@@ -101,10 +133,20 @@ class Morpheus::Cli::Remote
101
133
  end
102
134
 
103
135
  def use(args)
136
+ options = {}
137
+ optparse = OptionParser.new do|opts|
138
+ opts.banner = "Usage: morpheus remote use [name]"
139
+ build_common_options(opts, options, [])
140
+ opts.on( '-d', '--default', "Make this the default remote appliance. This does the same thing as remote use." ) do
141
+ options[:default] = true
142
+ end
143
+ end
144
+ optparse.parse(args)
104
145
  if args.empty?
105
- puts "Usage: morpheus remote use [name]"
146
+ puts "\n#{optparse.banner}\n\n"
106
147
  return
107
148
  end
149
+
108
150
  name = args[0].to_sym
109
151
  if @appliances[name] == nil
110
152
  print red, "Remote appliance not configured for #{args[0]}", reset, "\n"
@@ -141,12 +183,13 @@ class Morpheus::Cli::Remote
141
183
  if File.exist? remote_file
142
184
  return YAML.load_file(remote_file)
143
185
  else
144
- return {
145
- morpheus: {
146
- host: 'https://api.gomorpheus.com',
147
- active: true
148
- }
149
- }
186
+ return {}
187
+ # return {
188
+ # morpheus: {
189
+ # host: 'https://api.gomorpheus.com',
190
+ # active: true
191
+ # }
192
+ # }
150
193
  end
151
194
  end
152
195
 
@@ -23,6 +23,7 @@ class Morpheus::Cli::Roles
23
23
  exit 1
24
24
  end
25
25
  @api_client = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url)
26
+ @whoami_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).whoami
26
27
  @users_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).users
27
28
  @accounts_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).accounts
28
29
  @roles_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).roles
@@ -84,6 +85,8 @@ class Morpheus::Cli::Roles
84
85
  connect(options)
85
86
  begin
86
87
 
88
+ load_whoami()
89
+
87
90
  account = find_account_from_options(options)
88
91
  account_id = account ? account['id'] : nil
89
92
 
@@ -103,7 +106,7 @@ class Morpheus::Cli::Roles
103
106
  if roles.empty?
104
107
  puts yellow,"No roles found.",reset
105
108
  else
106
- print_roles_table(roles)
109
+ print_roles_table(roles, {is_master_account: @is_master_account})
107
110
  end
108
111
  print reset,"\n\n"
109
112
  end
@@ -168,6 +171,8 @@ class Morpheus::Cli::Roles
168
171
  puts "Name: #{role['authority']}"
169
172
  puts "Description: #{role['description']}"
170
173
  puts "Scope: #{role['scope']}"
174
+ puts "Type: #{format_role_type(role)}"
175
+ puts "Multitenant: #{role['multitenant'] ? 'Yes' : 'No'}"
171
176
  puts "Owner: #{role['owner'] ? role['owner']['name'] : nil}"
172
177
  puts "Date Created: #{format_local_dt(role['dateCreated'])}"
173
178
  puts "Last Updated: #{format_local_dt(role['lastUpdated'])}"
@@ -268,25 +273,55 @@ class Morpheus::Cli::Roles
268
273
  connect(options)
269
274
  begin
270
275
 
276
+ load_whoami()
277
+
271
278
  account = find_account_from_options(options)
272
279
  account_id = account ? account['id'] : nil
273
280
 
274
- params = Morpheus::Cli::OptionTypes.prompt(add_role_option_types, options[:options], @api_client, options[:params])
281
+ # argh, some options depend on others here...eg. multitenant is only available when roleType == 'user'
282
+ #prompt_option_types = update_role_option_types()
275
283
 
276
- #puts "parsed params is : #{params.inspect}"
277
- role_keys = ['authority', 'description', 'instanceLimits']
278
- role_payload = params.select {|k,v| role_keys.include?(k) }
279
- if !role_payload['instanceLimits']
280
- role_payload['instanceLimits'] = {}
281
- role_payload['instanceLimits']['maxStorage'] = params['instanceLimits.maxStorage'].to_i if params['instanceLimits.maxStorage'].to_s.strip != ''
282
- role_payload['instanceLimits']['maxMemory'] = params['instanceLimits.maxMemory'].to_i if params['instanceLimits.maxMemory'].to_s.strip != ''
283
- role_payload['instanceLimits']['maxCpu'] = params['instanceLimits.maxCpu'].to_i if params['instanceLimits.maxCpu'].to_s.strip != ''
284
+ role_payload = {}
285
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'authority', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1}], options[:options])
286
+ role_payload['authority'] = v_prompt['authority']
287
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'displayOrder' => 2}], options[:options])
288
+ role_payload['description'] = v_prompt['description']
289
+
290
+ if @is_master_account
291
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'roleType', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => role_type_options, 'defaultValue' => 'user', 'displayOrder' => 3}], options[:options])
292
+ role_payload['roleType'] = v_prompt['roleType']
293
+ else
294
+ role_payload['roleType'] = 'user'
284
295
  end
285
- if params['baseRole'].to_s != ''
286
- base_role = find_role_by_name(account_id, params['baseRole'])
296
+
297
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'baseRole', 'fieldLabel' => 'Copy From Role', 'type' => 'text', 'displayOrder' => 4}], options[:options])
298
+ if v_prompt['baseRole'].to_s != ''
299
+ base_role = find_role_by_name(account_id, v_prompt['baseRole'])
287
300
  exit 1 if base_role.nil?
288
301
  role_payload['baseRoleId'] = base_role['id']
289
302
  end
303
+
304
+ if @is_master_account
305
+ if role_payload['roleType'] == 'user'
306
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'multitenant', 'fieldLabel' => 'Multitenant', 'type' => 'checkbox', 'defaultValue' => 'off', 'description' => 'A Multitenant role is automatically copied into all existing subaccounts as well as placed into a subaccount when created. Useful for providing a set of predefined roles a Customer can use', 'displayOrder' => 5}], options[:options])
307
+ role_payload['multitenant'] = ['on','true'].include?(v_prompt['multitenant'].to_s)
308
+ end
309
+ end
310
+
311
+ role_payload['instanceLimits'] = {}
312
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'instanceLimits.maxStorage', 'fieldLabel' => 'Max Storage (bytes)', 'type' => 'text', 'displayOrder' => 8}], options[:options])
313
+ if v_prompt['instanceLimits.maxStorage'].to_s.strip != ''
314
+ role_payload['instanceLimits']['maxStorage'] = v_prompt['instanceLimits.maxStorage'].to_i
315
+ end
316
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'instanceLimits.maxMemory', 'fieldLabel' => 'Max Memory (bytes)', 'type' => 'text', 'displayOrder' => 9}], options[:options])
317
+ if v_prompt['instanceLimits.maxMemory'].to_s.strip != ''
318
+ role_payload['instanceLimits']['maxMemory'] = v_prompt['instanceLimits.maxMemory'].to_i
319
+ end
320
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'instanceLimits.maxCpu', 'fieldLabel' => 'CPU Count', 'type' => 'text', 'displayOrder' => 10}], options[:options])
321
+ if v_prompt['instanceLimits.maxCpu'].to_s.strip != ''
322
+ role_payload['instanceLimits']['maxCpu'] = v_prompt['instanceLimits.maxCpu'].to_i
323
+ end
324
+
290
325
  request_payload = {role: role_payload}
291
326
  response = @roles_interface.create(account_id, request_payload)
292
327
 
@@ -327,18 +362,27 @@ class Morpheus::Cli::Roles
327
362
 
328
363
  begin
329
364
 
365
+ load_whoami()
366
+
330
367
  account = find_account_from_options(options)
331
368
  account_id = account ? account['id'] : nil
332
369
 
333
370
  role = find_role_by_name(account_id, name)
334
371
  exit 1 if role.nil?
335
372
 
336
- #params = Morpheus::Cli::OptionTypes.prompt(update_role_option_types, options[:options], @api_client, options[:params])
373
+ prompt_option_types = update_role_option_types()
374
+ if !@is_master_account
375
+ prompt_option_types = prompt_option_types.reject {|it| ['roleType', 'multitenant'].include?(it['fieldName']) }
376
+ end
377
+ if role['roleType'] != 'user'
378
+ prompt_option_types = prompt_option_types.reject {|it| ['multitenant'].include?(it['fieldName']) }
379
+ end
380
+ #params = Morpheus::Cli::OptionTypes.prompt(prompt_option_types, options[:options], @api_client, options[:params])
337
381
  params = options[:options] || {}
338
382
 
339
383
  if params.empty?
340
384
  puts "\n#{usage}\n\n"
341
- option_lines = update_role_option_types.collect {|it| "\t-O #{it['fieldName']}=\"value\"" }.join("\n")
385
+ option_lines = prompt_option_types.collect {|it| "\t-O #{it['fieldName']}=\"value\"" }.join("\n")
342
386
  puts "\nAvailable Options:\n#{option_lines}\n\n"
343
387
  exit 1
344
388
  end
@@ -352,6 +396,10 @@ class Morpheus::Cli::Roles
352
396
  role_payload['instanceLimits']['maxMemory'] = params['instanceLimits.maxMemory'].to_i if params['instanceLimits.maxMemory'].to_s.strip != ''
353
397
  role_payload['instanceLimits']['maxCpu'] = params['instanceLimits.maxCpu'].to_i if params['instanceLimits.maxCpu'].to_s.strip != ''
354
398
  end
399
+
400
+ if params['multitenant'].to_s != ''
401
+ role_payload['multitenant'] = ['on','true'].include?(v_prompt['multitenant'].to_s)
402
+ end
355
403
  request_payload = {role: role_payload}
356
404
  response = @roles_interface.update(account_id, role['id'], request_payload)
357
405
 
@@ -785,28 +833,31 @@ class Morpheus::Cli::Roles
785
833
 
786
834
  private
787
835
 
788
- def get_access_string(val)
789
- val ||= 'none'
790
- if val == 'none'
791
- "#{white}#{val.to_s.capitalize}#{cyan}"
792
- else
793
- "#{green}#{val.to_s.capitalize}#{cyan}"
794
- end
795
- end
836
+ # def get_access_string(val)
837
+ # val ||= 'none'
838
+ # if val == 'none'
839
+ # "#{white}#{val.to_s.capitalize}#{cyan}"
840
+ # else
841
+ # "#{green}#{val.to_s.capitalize}#{cyan}"
842
+ # end
843
+ # end
796
844
 
797
845
  def add_role_option_types
798
846
  [
799
847
  {'fieldName' => 'authority', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
800
848
  {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'displayOrder' => 2},
801
- {'fieldName' => 'baseRole', 'fieldLabel' => 'Copy From Role', 'type' => 'text', 'displayOrder' => 3},
849
+ {'fieldName' => 'roleType', 'fieldLabel' => 'Role Type', 'type' => 'select', 'selectOptions' => [{'name' => 'User Role', 'value' => 'user'}, {'name' => 'Account Role', 'value' => 'account'}], 'defaultValue' => 'user', 'displayOrder' => 3},
850
+ {'fieldName' => 'baseRole', 'fieldLabel' => 'Copy From Role', 'type' => 'text', 'displayOrder' => 4},
851
+ {'fieldName' => 'multitenant', 'fieldLabel' => 'Multitenant', 'type' => 'checkbox', 'defaultValue' => 'off', 'description' => 'A Multitenant role is automatically copied into all existing subaccounts as well as placed into a subaccount when created. Useful for providing a set of predefined roles a Customer can use', 'displayOrder' => 5},
802
852
  {'fieldName' => 'instanceLimits.maxStorage', 'fieldLabel' => 'Max Storage (bytes)', 'type' => 'text', 'displayOrder' => 8},
803
853
  {'fieldName' => 'instanceLimits.maxMemory', 'fieldLabel' => 'Max Memory (bytes)', 'type' => 'text', 'displayOrder' => 9},
804
854
  {'fieldName' => 'instanceLimits.maxCpu', 'fieldLabel' => 'CPU Count', 'type' => 'text', 'displayOrder' => 10},
805
855
  ]
806
856
  end
807
857
 
858
+ "A Multitenant role is automatically copied into all existing subaccounts as well as placed into a subaccount when created. Useful for providing a set of predefined roles a Customer can use"
808
859
  def update_role_option_types
809
- add_role_option_types.reject {|it| ['baseRole'].include?(it['fieldName']) }
860
+ add_role_option_types.reject {|it| ['roleType', 'baseRole'].include?(it['fieldName']) }
810
861
  end
811
862
 
812
863
 
@@ -852,4 +903,18 @@ private
852
903
  return results['instanceTypes'][0]
853
904
  end
854
905
 
906
+ def load_whoami
907
+ whoami_response = @whoami_interface.get()
908
+ @current_user = whoami_response["user"]
909
+ if @current_user.empty?
910
+ print_red_alert "Unauthenticated. Please login."
911
+ exit 1
912
+ end
913
+ @is_master_account = whoami_response["isMasterAccount"]
914
+ end
915
+
916
+ def role_type_options
917
+ [{'name' => 'User Role', 'value' => 'user'}, {'name' => 'Account Role', 'value' => 'account'}]
918
+ end
919
+
855
920
  end
@@ -1,7 +1,6 @@
1
1
  # require 'yaml'
2
2
  require 'io/console'
3
3
  require 'rest_client'
4
- require 'term/ansicolor'
5
4
  require 'optparse'
6
5
  require 'table_print'
7
6
  require 'morpheus/cli/cli_command'
@@ -13,7 +12,7 @@ require 'fileutils'
13
12
 
14
13
  class Morpheus::Cli::Shell
15
14
  include Morpheus::Cli::CliCommand
16
- include Term::ANSIColor
15
+
17
16
  def initialize()
18
17
  @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
19
18
  @auto_complete = proc do |s|
@@ -55,7 +54,6 @@ class Morpheus::Cli::Shell
55
54
  @history_logger.info "shell started" if @history_logger
56
55
  load_history_from_log_file()
57
56
 
58
- remote_handler = Morpheus::Cli::Remote.new()
59
57
  exit = false
60
58
  while !exit do
61
59
  Readline.completion_append_character = " "
@@ -122,13 +120,18 @@ class Morpheus::Cli::Shell
122
120
  if @command_options[:nocolor]
123
121
  argv.push "--nocolor"
124
122
  end
123
+
124
+ # set global log level to debug (print stack trace for bubbled exceptions)
125
+ if argv.find {|arg| arg == '-V' || arg == '--debug'}
126
+ @return_to_log_level = Morpheus::Logging.log_level
127
+ Morpheus::Logging.set_log_level(Morpheus::Logging::Logger::DEBUG)
128
+ end
129
+ argv = argv.find_all {|arg| arg != '-V' && arg != '--debug'}
130
+
125
131
  #puts "cmd: #{argv.join(' ')}"
126
132
 
127
133
  if argv[0] == 'shell'
128
- puts "Unrecognized Command."
129
- elsif argv[0] == 'remote'
130
- log_history_command(input)
131
- remote_handler.handle(argv[1..-1])
134
+ puts "You are already in a shell."
132
135
  elsif Morpheus::Cli::CliRegistry.has_command?(argv[0])
133
136
  log_history_command(input)
134
137
  Morpheus::Cli::CliRegistry.exec(argv[0], argv[1..-1])
@@ -145,13 +148,23 @@ class Morpheus::Cli::Shell
145
148
  rescue SystemExit
146
149
  # nothing to do
147
150
  print "\n"
151
+ rescue OptionParser::InvalidOption => e
152
+ print Term::ANSIColor.red, "\n", "#{e.message}", "", Term::ANSIColor.reset
153
+ print "\n", "Try -h for help with this command.", "\n\n"
148
154
  rescue => e
149
-
150
155
  @history_logger.error "#{e.message}" if @history_logger
151
- print red, "\n", e.message, "\n", reset
152
- print e.backtrace.join("\n"), "\n"
156
+ print Term::ANSIColor.red, "\n", "Unexpected Error", "\n\n", Term::ANSIColor.reset
157
+ if Morpheus::Logging.print_stacktrace?
158
+ print Term::ANSIColor.red, "\n", "#{e.class}: #{e.message}", "\n", Term::ANSIColor.reset
159
+ print e.backtrace.join("\n"), "\n\n"
160
+ end
153
161
  end
154
-
162
+
163
+ if @return_to_log_level
164
+ Morpheus::Logging.set_log_level(@return_to_log_level)
165
+ @return_to_log_level = nil
166
+ end
167
+
155
168
  end
156
169
  end
157
170
  end
@@ -23,6 +23,7 @@ class Morpheus::Cli::Users
23
23
  exit 1
24
24
  end
25
25
  @api_client = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url)
26
+ @whoami_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).whoami
26
27
  @users_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).users
27
28
  @accounts_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).accounts
28
29
  @roles_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).roles
@@ -46,6 +47,8 @@ class Morpheus::Cli::Users
46
47
  update(args[1..-1])
47
48
  when 'remove'
48
49
  remove(args[1..-1])
50
+ when 'feature-permissions'
51
+ feature_permissions(args[1..-1])
49
52
  else
50
53
  puts "\n#{usage}\n\n"
51
54
  exit 127
@@ -84,7 +87,7 @@ class Morpheus::Cli::Users
84
87
  else
85
88
  print_users_table(users)
86
89
  end
87
- print reset,"\n\n"
90
+ print reset,"\n"
88
91
  end
89
92
  rescue RestClient::Exception => e
90
93
  print_rest_exception(e, options)
@@ -98,6 +101,24 @@ class Morpheus::Cli::Users
98
101
  optparse = OptionParser.new do|opts|
99
102
  opts.banner = usage
100
103
  build_common_options(opts, options, [:account, :json])
104
+ opts.on(nil,'--feature-access', "Display Feature Access") do |val|
105
+ options[:include_feature_access] = true
106
+ end
107
+ # opts.on(nil,'--group-access', "Display Group Access") do
108
+ # options[:include_group_access] = true
109
+ # end
110
+ # opts.on(nil,'--cloud-access', "Display Cloud Access") do
111
+ # options[:include_cloud_access] = true
112
+ # end
113
+ # opts.on(nil,'--instance-type-access', "Display Instance Type Access") do
114
+ # options[:include_instance_type_access] = true
115
+ # end
116
+ opts.on(nil,'--all-access', "Display All Access Lists") do
117
+ options[:include_feature_access] = true
118
+ options[:include_group_access] = true
119
+ options[:include_cloud_access] = true
120
+ options[:include_instance_type_access] = true
121
+ end
101
122
  end
102
123
  optparse.parse(args)
103
124
 
@@ -118,18 +139,31 @@ class Morpheus::Cli::Users
118
139
  user = find_user_by_username(account_id, username)
119
140
  exit 1 if user.nil?
120
141
 
142
+ # meh, this should just always be returned with GET /api/users/:id
143
+ user_feature_permissions_json = nil
144
+ user_feature_permissions = nil
145
+ if options[:include_feature_access]
146
+ user_feature_permissions_json = @users_interface.feature_permissions(account_id, user['id'])
147
+ user_feature_permissions = user_feature_permissions_json['featurePermissions']
148
+ end
149
+
121
150
  if options[:json]
122
151
  print JSON.pretty_generate({user:user})
123
152
  print "\n"
153
+ if (user_feature_permissions_json)
154
+ print JSON.pretty_generate(user_feature_permissions_json)
155
+ print "\n"
156
+ end
124
157
  else
125
158
  print "\n" ,cyan, bold, "User Details\n","==================", reset, "\n\n"
126
159
  print cyan
127
160
  puts "ID: #{user['id']}"
128
161
  puts "Account: #{user['account'] ? user['account']['name'] : nil}"
129
162
  puts "First Name: #{user['firstName']}"
130
- puts "Last Name: #{user['firstName']}"
163
+ puts "Last Name: #{user['lastName']}"
131
164
  puts "Username: #{user['username']}"
132
- puts "Role: #{user['role'] ? user['role']['authority'] : nil}"
165
+ puts "Email: #{user['email']}"
166
+ puts "Role: #{format_user_role_names(user)}"
133
167
  puts "Date Created: #{format_local_dt(user['dateCreated'])}"
134
168
  puts "Last Updated: #{format_local_dt(user['lastUpdated'])}"
135
169
  print "\n" ,cyan, bold, "User Instance Limits\n","==================", reset, "\n\n"
@@ -137,8 +171,22 @@ class Morpheus::Cli::Users
137
171
  puts "Max Storage (bytes): #{user['instanceLimits'] ? user['instanceLimits']['maxStorage'] : 0}"
138
172
  puts "Max Memory (bytes): #{user['instanceLimits'] ? user['instanceLimits']['maxMemory'] : 0}"
139
173
  puts "CPU Count: #{user['instanceLimits'] ? user['instanceLimits']['maxCpu'] : 0}"
174
+
175
+ if options[:include_feature_access] && user_feature_permissions
176
+ if user_feature_permissions
177
+ print "\n" ,cyan, bold, "Feature Permissions\n","==================", reset, "\n\n"
178
+ print cyan
179
+ rows = user_feature_permissions.collect do |code, access|
180
+ {code: code, access: get_access_string(access) }
181
+ end
182
+ tp rows, [:code, :access]
183
+ else
184
+ puts yellow,"No permissions found.",reset
185
+ end
186
+ end
187
+
140
188
  print cyan
141
- print reset,"\n\n"
189
+ print reset,"\n"
142
190
  end
143
191
  rescue RestClient::Exception => e
144
192
  print_rest_exception(e, options)
@@ -152,6 +200,11 @@ class Morpheus::Cli::Users
152
200
  optparse = OptionParser.new do|opts|
153
201
  opts.banner = usage
154
202
  build_common_options(opts, options, [:account, :options, :json])
203
+ opts.on('-h', '--help', "Prints this help" ) do
204
+ puts opts
205
+ puts Morpheus::Cli::OptionTypes.display_option_types_help(add_user_option_types)
206
+ exit
207
+ end
155
208
  end
156
209
  optparse.parse(args)
157
210
 
@@ -162,7 +215,10 @@ class Morpheus::Cli::Users
162
215
  account = find_account_from_options(options)
163
216
  account_id = account ? account['id'] : nil
164
217
 
165
- params = Morpheus::Cli::OptionTypes.prompt(add_user_option_types, options[:options], @api_client, options[:params])
218
+ # remove role option_type, it is just for help display, the role prompt is separate down below
219
+ prompt_option_types = add_user_option_types().reject {|it| ['role'].include?(it['fieldName']) }
220
+
221
+ params = Morpheus::Cli::OptionTypes.prompt(prompt_option_types, options[:options], @api_client, options[:params])
166
222
 
167
223
  #puts "parsed params is : #{params.inspect}"
168
224
  user_keys = ['username', 'firstName', 'lastName', 'email', 'password', 'passwordConfirmation', 'instanceLimits']
@@ -173,11 +229,12 @@ class Morpheus::Cli::Users
173
229
  user_payload['instanceLimits']['maxMemory'] = params['instanceLimits.maxMemory'].to_i if params['instanceLimits.maxMemory'].to_s.strip != ''
174
230
  user_payload['instanceLimits']['maxCpu'] = params['instanceLimits.maxCpu'].to_i if params['instanceLimits.maxCpu'].to_s.strip != ''
175
231
  end
176
- if params['role'].to_s != ''
177
- role = find_role_by_name(account_id, params['role'])
178
- exit 1 if role.nil?
179
- user_payload['role'] = {id: role['id']}
232
+
233
+ roles = prompt_user_roles(account_id, nil, options)
234
+ if !roles.empty?
235
+ user_payload['roles'] = roles.collect {|r| {id: r['id']} }
180
236
  end
237
+
181
238
  request_payload = {user: user_payload}
182
239
  json_response = @users_interface.create(account_id, request_payload)
183
240
 
@@ -210,11 +267,17 @@ class Morpheus::Cli::Users
210
267
  optparse = OptionParser.new do|opts|
211
268
  opts.banner = usage
212
269
  build_common_options(opts, options, [:account, :options, :json])
270
+ opts.on('-h', '--help', "Prints this help" ) do
271
+ puts opts
272
+ puts Morpheus::Cli::OptionTypes.display_option_types_help(update_user_option_types)
273
+ exit
274
+ end
213
275
  end
214
276
  optparse.parse(args)
215
277
 
216
278
  if args.count < 1
217
- puts "\n#{usage}\n\n"
279
+ puts "#{usage}\n\n"
280
+ puts Morpheus::Cli::OptionTypes.display_option_types_help(update_user_option_types)
218
281
  exit 1
219
282
  end
220
283
  username = args[0]
@@ -231,16 +294,19 @@ class Morpheus::Cli::Users
231
294
 
232
295
  #params = Morpheus::Cli::OptionTypes.prompt(update_user_option_types, options[:options], @api_client, options[:params])
233
296
  params = options[:options] || {}
234
-
297
+ roles = prompt_user_roles(account_id, user['id'], options.merge(no_prompt: true))
298
+ if !roles.empty?
299
+ params['roles'] = roles.collect {|r| {id: r['id']} }
300
+ end
301
+
235
302
  if params.empty?
236
303
  puts "\n#{usage}\n\n"
237
- option_lines = update_user_option_types.collect {|it| "\t-O #{it['fieldName']}=\"value\"" }.join("\n")
238
- puts "\nAvailable Options:\n#{option_lines}\n\n"
304
+ puts Morpheus::Cli::OptionTypes.display_option_types_help(update_user_option_types)
239
305
  exit 1
240
306
  end
241
307
 
242
308
  #puts "parsed params is : #{params.inspect}"
243
- user_keys = ['username', 'firstName', 'lastName', 'email', 'password', 'instanceLimits']
309
+ user_keys = ['username', 'firstName', 'lastName', 'email', 'password', 'instanceLimits', 'roles']
244
310
  user_payload = params.select {|k,v| user_keys.include?(k) }
245
311
  if !user_payload['instanceLimits']
246
312
  user_payload['instanceLimits'] = {}
@@ -248,11 +314,7 @@ class Morpheus::Cli::Users
248
314
  user_payload['instanceLimits']['maxMemory'] = params['instanceLimits.maxMemory'].to_i if params['instanceLimits.maxMemory'].to_s.strip != ''
249
315
  user_payload['instanceLimits']['maxCpu'] = params['instanceLimits.maxCpu'].to_i if params['instanceLimits.maxCpu'].to_s.strip != ''
250
316
  end
251
- if params['role'].to_s != ''
252
- role = find_role_by_name(account_id, params['role'])
253
- exit 1 if role.nil?
254
- user_payload['role'] = {id: role['id']}
255
- end
317
+
256
318
  request_payload = {user: user_payload}
257
319
  json_response = @users_interface.update(account_id, user['id'], request_payload)
258
320
 
@@ -320,21 +382,101 @@ private
320
382
 
321
383
  def add_user_option_types
322
384
  [
323
- {'fieldName' => 'username', 'fieldLabel' => 'Username', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
324
- {'fieldName' => 'firstName', 'fieldLabel' => 'First Name', 'type' => 'text', 'required' => true, 'displayOrder' => 2},
325
- {'fieldName' => 'lastName', 'fieldLabel' => 'Last Name', 'type' => 'text', 'required' => true, 'displayOrder' => 3},
385
+ {'fieldName' => 'firstName', 'fieldLabel' => 'First Name', 'type' => 'text', 'required' => false, 'displayOrder' => 1},
386
+ {'fieldName' => 'lastName', 'fieldLabel' => 'Last Name', 'type' => 'text', 'required' => false, 'displayOrder' => 2},
387
+ {'fieldName' => 'username', 'fieldLabel' => 'Username', 'type' => 'text', 'required' => true, 'displayOrder' => 3},
326
388
  {'fieldName' => 'email', 'fieldLabel' => 'Email', 'type' => 'text', 'required' => true, 'displayOrder' => 4},
327
- {'fieldName' => 'role', 'fieldLabel' => 'Role', 'type' => 'text', 'displayOrder' => 5},
328
389
  {'fieldName' => 'password', 'fieldLabel' => 'Password', 'type' => 'password', 'required' => true, 'displayOrder' => 6},
329
390
  {'fieldName' => 'passwordConfirmation', 'fieldLabel' => 'Confirm Password', 'type' => 'password', 'required' => true, 'displayOrder' => 7},
330
391
  {'fieldName' => 'instanceLimits.maxStorage', 'fieldLabel' => 'Max Storage (bytes)', 'type' => 'text', 'displayOrder' => 8},
331
392
  {'fieldName' => 'instanceLimits.maxMemory', 'fieldLabel' => 'Max Memory (bytes)', 'type' => 'text', 'displayOrder' => 9},
332
393
  {'fieldName' => 'instanceLimits.maxCpu', 'fieldLabel' => 'CPU Count', 'type' => 'text', 'displayOrder' => 10},
394
+ {'fieldName' => 'role', 'fieldLabel' => 'Role', 'type' => 'text', 'displayOrder' => 11, 'description' => "Role names (comma separated)"},
333
395
  ]
334
396
  end
335
397
 
336
398
  def update_user_option_types
337
- add_user_option_types.reject {|it| ['passwordConfirmation'].include?(it['fieldName']) }
399
+ [
400
+ {'fieldName' => 'firstName', 'fieldLabel' => 'First Name', 'type' => 'text', 'required' => false, 'displayOrder' => 1},
401
+ {'fieldName' => 'lastName', 'fieldLabel' => 'Last Name', 'type' => 'text', 'required' => false, 'displayOrder' => 2},
402
+ {'fieldName' => 'username', 'fieldLabel' => 'Username', 'type' => 'text', 'required' => false, 'displayOrder' => 3},
403
+ {'fieldName' => 'email', 'fieldLabel' => 'Email', 'type' => 'text', 'required' => false, 'displayOrder' => 4},
404
+ {'fieldName' => 'password', 'fieldLabel' => 'Password', 'type' => 'password', 'required' => false, 'displayOrder' => 6},
405
+ {'fieldName' => 'passwordConfirmation', 'fieldLabel' => 'Confirm Password', 'type' => 'password', 'required' => false, 'displayOrder' => 7},
406
+ {'fieldName' => 'instanceLimits.maxStorage', 'fieldLabel' => 'Max Storage (bytes)', 'type' => 'text', 'displayOrder' => 8},
407
+ {'fieldName' => 'instanceLimits.maxMemory', 'fieldLabel' => 'Max Memory (bytes)', 'type' => 'text', 'displayOrder' => 9},
408
+ {'fieldName' => 'instanceLimits.maxCpu', 'fieldLabel' => 'CPU Count', 'type' => 'text', 'displayOrder' => 10},
409
+ {'fieldName' => 'role', 'fieldLabel' => 'Role', 'type' => 'text', 'displayOrder' => 11, 'description' => "Role names (comma separated)"},
410
+ ]
411
+ end
412
+
413
+ # prompt user to select roles for a new or existing user
414
+ # options['role'] can be passed as comma separated role names
415
+ # if so, it will be used instead of prompting
416
+ # returns array of role objects
417
+ def prompt_user_roles(account_id, user_id, options={})
418
+
419
+ passed_role_string = nil
420
+ if options['role'] || (options[:options] && (options[:options]['role'] || options[:options]['roles']))
421
+ passed_role_string = options['role'] || (options[:options] && (options[:options]['role'] || options[:options]['roles']))
422
+ end
423
+ passed_role_names = []
424
+ if !passed_role_string.empty?
425
+ passed_role_names = passed_role_string.split(',').uniq.compact.collect {|r| r.strip}
426
+ end
427
+
428
+ available_roles = @users_interface.available_roles(account_id, user_id)['roles']
429
+
430
+ if available_roles.empty?
431
+ print_red_alert "No available roles found."
432
+ exit 1
433
+ end
434
+ role_options = available_roles.collect {|role|
435
+ {'name' => role['authority'], 'value' => role['id']}
436
+ }
437
+
438
+ # found_roles = []
439
+ roles = []
440
+
441
+ if !passed_role_names.empty?
442
+ invalid_role_names = []
443
+ passed_role_names.each do |role_name|
444
+ found_role = available_roles.find {|ar| ar['authority'] == role_name}
445
+ if found_role
446
+ # found_roles << found_role
447
+ roles << found_role
448
+ else
449
+ invalid_role_names << role_name
450
+ end
451
+ end
452
+ if !invalid_role_names.empty?
453
+ print_red_alert "Invalid Roles: #{invalid_role_names.join(', ')}"
454
+ exit 1
455
+ end
456
+ end
457
+
458
+ if roles.empty?
459
+ no_prompt = (options[:no_prompt] || (options[:options] && options[:options][:no_prompt]))
460
+ if !no_prompt
461
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'roleId', 'fieldLabel' => 'Role', 'type' => 'select', 'selectOptions' => role_options, 'required' => true}], options[:options])
462
+ role_id = v_prompt['roleId']
463
+ roles << available_roles.find {|r| r['id'].to_i == role_id.to_i }
464
+ add_another_role = true
465
+ while add_another_role do
466
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'roleId', 'fieldLabel' => 'Another Role', 'type' => 'select', 'selectOptions' => role_options, 'required' => false}], options[:options])
467
+ if v_prompt['roleId'].to_s.empty?
468
+ add_another_role = false
469
+ else
470
+ role_id = v_prompt['roleId']
471
+ roles << available_roles.find {|r| r['id'].to_i == role_id.to_i }
472
+ end
473
+ end
474
+ end
475
+ end
476
+
477
+ roles = roles.compact
478
+ return roles
479
+
338
480
  end
339
481
 
340
482
  end