morpheus-cli 0.9.10 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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