morpheus-cli 3.3.1.4 → 3.3.2

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/morpheus/api/api_client.rb +28 -0
  3. data/lib/morpheus/api/instance_types_interface.rb +12 -10
  4. data/lib/morpheus/api/instances_interface.rb +4 -0
  5. data/lib/morpheus/api/library_container_scripts_interface.rb +49 -0
  6. data/lib/morpheus/api/library_container_templates_interface.rb +49 -0
  7. data/lib/morpheus/api/library_container_types_interface.rb +65 -0
  8. data/lib/morpheus/api/library_container_upgrades_interface.rb +66 -0
  9. data/lib/morpheus/api/library_instance_types_interface.rb +59 -0
  10. data/lib/morpheus/api/library_layouts_interface.rb +65 -0
  11. data/lib/morpheus/api/servers_interface.rb +4 -0
  12. data/lib/morpheus/api/user_sources_interface.rb +120 -0
  13. data/lib/morpheus/api/virtual_images_interface.rb +7 -0
  14. data/lib/morpheus/cli.rb +12 -1
  15. data/lib/morpheus/cli/accounts.rb +35 -9
  16. data/lib/morpheus/cli/cli_command.rb +82 -2
  17. data/lib/morpheus/cli/curl_command.rb +1 -1
  18. data/lib/morpheus/cli/echo_command.rb +1 -1
  19. data/lib/morpheus/cli/hosts.rb +40 -14
  20. data/lib/morpheus/cli/instance_types.rb +106 -64
  21. data/lib/morpheus/cli/instances.rb +39 -15
  22. data/lib/morpheus/cli/library.rb +1 -1184
  23. data/lib/morpheus/cli/library_container_scripts_command.rb +437 -0
  24. data/lib/morpheus/cli/library_container_templates_command.rb +397 -0
  25. data/lib/morpheus/cli/library_container_types_command.rb +653 -0
  26. data/lib/morpheus/cli/library_instance_types_command.rb +491 -0
  27. data/lib/morpheus/cli/library_layouts_command.rb +650 -0
  28. data/lib/morpheus/cli/library_option_lists_command.rb +476 -0
  29. data/lib/morpheus/cli/library_option_types_command.rb +549 -0
  30. data/lib/morpheus/cli/library_upgrades_command.rb +604 -0
  31. data/lib/morpheus/cli/mixins/library_helper.rb +123 -0
  32. data/lib/morpheus/cli/mixins/print_helper.rb +21 -22
  33. data/lib/morpheus/cli/mixins/provisioning_helper.rb +56 -11
  34. data/lib/morpheus/cli/network_services_command.rb +1 -1
  35. data/lib/morpheus/cli/option_types.rb +12 -2
  36. data/lib/morpheus/cli/power_scheduling_command.rb +1 -1
  37. data/lib/morpheus/cli/shell.rb +120 -22
  38. data/lib/morpheus/cli/sleep_command.rb +45 -0
  39. data/lib/morpheus/cli/user_sources_command.rb +963 -0
  40. data/lib/morpheus/cli/users.rb +33 -2
  41. data/lib/morpheus/cli/version.rb +1 -1
  42. data/lib/morpheus/cli/version_command.rb +1 -1
  43. data/lib/morpheus/cli/virtual_images.rb +93 -39
  44. data/lib/morpheus/formatters.rb +37 -27
  45. data/lib/morpheus/terminal.rb +1 -1
  46. metadata +20 -2
@@ -0,0 +1,45 @@
1
+ require 'morpheus/cli/cli_command'
2
+ require 'term/ansicolor'
3
+ require 'json'
4
+
5
+ # This is for use in dotfile scripts
6
+ class Morpheus::Cli::Sleep
7
+ include Morpheus::Cli::CliCommand
8
+ set_command_name :sleep
9
+ set_command_hidden
10
+
11
+ def handle(args)
12
+ append_newline = true
13
+ options = {}
14
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
15
+ opts.banner = "Usage: morpheus sleep [seconds]"
16
+ build_common_options(opts, options, [:quiet])
17
+ end
18
+ optparse.parse!(args)
19
+
20
+ sleep_seconds = args[0]
21
+
22
+ # if !sleep_seconds
23
+ # print_error Morpheus::Terminal.angry_prompt
24
+ # puts_error "#{command_name} missing argument: [seconds]\n#{optparse}"
25
+ # return 1
26
+ # end
27
+
28
+ if !sleep_seconds
29
+ sleep_seconds = 1.0
30
+ end
31
+
32
+ # do it
33
+ if !options[:quiet]
34
+ if options[:debug]
35
+ # puts "Sleep for #{sleep_seconds.to_f} seconds..."
36
+ Morpheus::Logging::DarkPrinter.puts "sleep #{sleep_seconds.to_f} seconds..." if Morpheus::Logging.debug?
37
+ end
38
+ end
39
+
40
+ sleep(sleep_seconds.to_f)
41
+
42
+ return 0
43
+ end
44
+
45
+ end
@@ -0,0 +1,963 @@
1
+ require 'morpheus/cli/cli_command'
2
+ require 'morpheus/cli/mixins/accounts_helper'
3
+
4
+ class Morpheus::Cli::UserSourcesCommand
5
+ include Morpheus::Cli::CliCommand
6
+ include Morpheus::Cli::AccountsHelper
7
+
8
+ set_command_name :'user-sources'
9
+
10
+ register_subcommands :list, :get, :add, :update, :remove
11
+ register_subcommands :activate, :deactivate
12
+ register_subcommands({:'update-subdomain' => :update_subdomain})
13
+ register_subcommands({:'list-types' => :list_types})
14
+ register_subcommands({:'get-type' => :get_type})
15
+
16
+ def initialize()
17
+ # @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
18
+ end
19
+
20
+ def connect(opts)
21
+ @api_client = establish_remote_appliance_connection(opts)
22
+ @user_sources_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).user_sources
23
+ @accounts_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).accounts
24
+ @users_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).users
25
+ end
26
+
27
+ def handle(args)
28
+ handle_subcommand(args)
29
+ end
30
+
31
+
32
+ def list(args)
33
+ options = {}
34
+ params = {}
35
+ account = nil
36
+ account_id = nil
37
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
38
+ opts.banner = subcommand_usage()
39
+ opts.on('--account ID', String, "Filter by Tenant") do |val|
40
+ account_id = val
41
+ end
42
+ # opts.on('--technology VALUE', String, "Filter by technology") do |val|
43
+ # params['provisionType'] = val
44
+ # end
45
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
46
+ opts.footer = "List user sources."
47
+ end
48
+ optparse.parse!(args)
49
+ connect(options)
50
+ # instance is required right now.
51
+ # account_id = args[0] if !account_id
52
+ if args.count > 0
53
+ print_error Morpheus::Terminal.angry_prompt
54
+ puts_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
55
+ return 1
56
+ end
57
+ begin
58
+ # construct payload
59
+ if account_id
60
+ account = find_account_by_name_or_id(account_id)
61
+ return 1 if account.nil?
62
+ account_id = account['id']
63
+ end
64
+
65
+ params.merge!(parse_list_options(options))
66
+
67
+ if options[:dry_run]
68
+ print_dry_run @user_sources_interface.dry.list(account_id, params)
69
+ return
70
+ end
71
+
72
+ json_response = @user_sources_interface.list(account_id, params)
73
+ if options[:include_fields]
74
+ json_response = {"userSources" => filter_data(json_response["userSources"], options[:include_fields]) }
75
+ end
76
+ if options[:json]
77
+ puts as_json(json_response, options)
78
+ return 0
79
+ elsif options[:csv]
80
+ puts records_as_csv(json_response['userSources'], options)
81
+ return 0
82
+ elsif options[:yaml]
83
+ puts as_yaml(json_response, options)
84
+ return 0
85
+ end
86
+ user_sources = json_response['userSources']
87
+ title = "Morpheus User Sources"
88
+ subtitles = []
89
+ if account
90
+ subtitles << "Account: #{account['name']}".strip
91
+ end
92
+ subtitles += parse_list_subtitles(options)
93
+ print_h1 title, subtitles
94
+ if user_sources.empty?
95
+ if account
96
+ print cyan,"No user sources found for account #{account['name']}.",reset,"\n"
97
+ else
98
+ print cyan,"No user sources found.",reset,"\n"
99
+ end
100
+ else
101
+ print_user_sources_table(user_sources, options)
102
+ print_results_pagination(json_response, {:label => "user source", :n_label => "user sources"})
103
+ end
104
+ print reset,"\n"
105
+ rescue RestClient::Exception => e
106
+ print_rest_exception(e, options)
107
+ return 1
108
+ end
109
+ end
110
+
111
+ def get(args)
112
+ options = {}
113
+ params = {}
114
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
115
+ opts.banner = subcommand_usage("[name]")
116
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
117
+ opts.footer = "Get details about an user source." + "\n" +
118
+ "[name] is required. This is the name or id of an user source."
119
+ end
120
+ optparse.parse!(args)
121
+ connect(options)
122
+ if args.count != 1
123
+ print_error Morpheus::Terminal.angry_prompt
124
+ puts_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
125
+ return 1
126
+ end
127
+ account_id = nil
128
+ account = nil
129
+ user_source_id = args[0]
130
+ # account_id = args[0]
131
+ # account = find_account_by_name_or_id(account_id)
132
+ # exit 1 if account.nil?
133
+ # account_id = account['id']
134
+ # user_source_id = args[1]
135
+ begin
136
+ if options[:dry_run]
137
+ if user_source_id.to_s =~ /\A\d{1,}\Z/
138
+ print_dry_run @user_sources_interface.dry.get(account_id, user_source_id.to_i)
139
+ else
140
+ print_dry_run @user_sources_interface.dry.list(account_id, {name:user_source_id})
141
+ end
142
+ return
143
+ end
144
+ user_source = find_user_source_by_name_or_id(account_id, user_source_id)
145
+ if user_source.nil?
146
+ return 1
147
+ end
148
+ # fetch by id to get config too
149
+ json_response = nil
150
+ if user_source_id.to_s =~ /\A\d{1,}\Z/
151
+ json_response = {'userSource' => user_source}
152
+ else
153
+ json_response = @user_sources_interface.get(account_id, user_source['id'])
154
+ user_source = json_response['userSource']
155
+ end
156
+
157
+ #user_source = json_response['userSource']
158
+ if options[:include_fields]
159
+ json_response = {"userSource" => filter_data(json_response["userSource"], options[:include_fields]) }
160
+ end
161
+ if options[:json]
162
+ puts as_json(json_response, options)
163
+ return 0
164
+ elsif options[:yaml]
165
+ puts as_yaml(json_response, options)
166
+ return 0
167
+ elsif options[:csv]
168
+ puts records_as_csv([json_response['userSource']], options)
169
+ return 0
170
+ end
171
+
172
+ print_h1 "User Source Details"
173
+ print cyan
174
+ description_cols = {
175
+ "ID" => lambda {|it| it['id'] },
176
+ "Name" => lambda {|it| it['name'] },
177
+ "Description" => lambda {|it| it['description'] },
178
+ "Type" => lambda {|it| it['type'] },
179
+ "Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
180
+ #"Subdomain" => lambda {|it| it['subdomain'] },
181
+ "Login URL" => lambda {|it| it['loginURL'] },
182
+ "Default Role" => lambda {|it| it['defaultAccountRole'] ? it['defaultAccountRole']['authority'] : '' },
183
+ "Active" => lambda {|it| format_boolean it['active'] },
184
+ }
185
+ print_description_list(description_cols, user_source)
186
+
187
+ # show config settings...
188
+ user_source_config = user_source['config']
189
+ print_h2 "#{user_source['type']} Configuration"
190
+ if user_source_config
191
+ columns = user_source_config.keys #.sort
192
+ print_description_list(columns, user_source_config)
193
+ # print reset,"\n"
194
+ else
195
+ print yellow,"No config found.","\n",reset
196
+ end
197
+
198
+ role_mappings = user_source['roleMappings']
199
+ print_h2 "Role Mappings"
200
+ if role_mappings && role_mappings.size > 0
201
+ # print_h2 "Role Mappings"
202
+ role_mapping_columns = [
203
+ {"MORPHEUS ROLE" => lambda {|it|
204
+ it['mappedRole'] ? it['mappedRole']['authority'] : ''
205
+ } },
206
+ {"SOURCE ROLE NAME" => lambda {|it| it['sourceRoleName'] } },
207
+ {"SOURCE ROLE FQN" => lambda {|it| it['sourceRoleFqn'] } },
208
+ ]
209
+ print as_pretty_table(role_mappings, role_mapping_columns)
210
+ print "\n",reset
211
+ else
212
+ print yellow,"No role mappings found for this user source.","\n",reset
213
+ end
214
+ return 0
215
+ rescue RestClient::Exception => e
216
+ print_rest_exception(e, options)
217
+ return 1
218
+ end
219
+ end
220
+
221
+ def add(args)
222
+ options = {}
223
+ params = {}
224
+ account_id = nil
225
+ type_code = nil
226
+ role_mappings = nil
227
+ role_mapping_names = nil
228
+ default_role_id = nil
229
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
230
+ opts.banner = subcommand_usage("[account] [name]")
231
+ opts.on('--account ID', String, "Account this user source belongs to") do |val|
232
+ account_id = val
233
+ end
234
+ opts.on('--type CODE', String, "User Source Type") do |val|
235
+ type_code = val
236
+ end
237
+ opts.on('--name VALUE', String, "Name for this user source") do |val|
238
+ params['name'] = val
239
+ end
240
+ opts.on('--description VALUE', String, "Description") do |val|
241
+ params['description'] = val
242
+ end
243
+ opts.on('--role-mappings MAPPINGS', String, "Role Mappings FQN in the format id1:FQN1,id2:FQN2") do |val|
244
+ role_mappings = {}
245
+ val.split(',').collect {|it| it.strip.split(':') }.each do |pair|
246
+ k, v = pair[0], pair[1]
247
+ if !k.to_s.empty?
248
+ role_mappings[k.to_s] = v
249
+ end
250
+ end
251
+ end
252
+ opts.on('--role-mapping-names MAPPINGS', String, "Role Mapping Names in the format id1:Name1,id2:Name2") do |val|
253
+ role_mapping_names = {}
254
+ val.split(',').collect {|it| it.strip.split(':') }.each do |pair|
255
+ k, v = pair[0], pair[1]
256
+ if !k.to_s.empty?
257
+ role_mapping_names[k.to_s] = v
258
+ end
259
+ end
260
+ end
261
+
262
+ opts.on('--default-role ID', String, "Default Role ID") do |val|
263
+ default_role_id = val
264
+ end
265
+ #build_option_type_options(opts, options, add_user_source_option_types())
266
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
267
+ opts.footer = "Create a new user source." + "\n" +
268
+ "[account] is required. This is the name or id of an account."
269
+ end
270
+ optparse.parse!(args)
271
+ connect(options)
272
+ if args.count > 2
273
+ print_error Morpheus::Terminal.angry_prompt
274
+ puts_error "wrong number of arguments, expected 0-2 and got (#{args.count}) #{args.inspect}\n#{optparse}"
275
+ return 1
276
+ end
277
+ if args[0]
278
+ account_id = args[0]
279
+ end
280
+ if args[1]
281
+ params['name'] = args[1]
282
+ end
283
+ begin
284
+ # find the account first, or just prompt for that too please.
285
+ if !account_id
286
+ print_error Morpheus::Terminal.angry_prompt
287
+ puts_error "missing required argument [account]\n#{optparse}"
288
+ return 1
289
+ end
290
+ account = find_account_by_name_or_id(account_id)
291
+ return 1 if account.nil?
292
+ account_id = account['id']
293
+
294
+ # construct payload
295
+ payload = nil
296
+ if options[:payload]
297
+ payload = options[:payload]
298
+ else
299
+ payload = {'userSource' => {}}
300
+
301
+ # User Source Type
302
+ user_source_types = @user_sources_interface.list_types({userSelectable: true})['userSourceTypes']
303
+ if user_source_types.empty?
304
+ print_red_alert "No available User Source Types found"
305
+ return 1
306
+ end
307
+ user_source_type = nil
308
+ if !type_code
309
+ user_source_type_dropdown = user_source_types.collect {|it| { 'name' => it['type'], 'value' => it['type']} }
310
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'type', 'type' => 'select', 'selectOptions' => user_source_type_dropdown, 'fieldLabel' => 'Type', 'required' => true}], options[:options])
311
+ type_code = v_prompt['type'] if v_prompt['type']
312
+ end
313
+ user_source_type = user_source_types.find { |it| it['type'] == type_code }
314
+
315
+ if user_source_type.nil?
316
+ print_red_alert "User Source Type not found for '#{type_code}'"
317
+ return 1
318
+ end
319
+
320
+ payload['userSource']['type'] = type_code
321
+
322
+ # Name
323
+ if params['name']
324
+ payload['userSource']['name'] = params['name']
325
+ else
326
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'type' => 'text', 'fieldLabel' => 'Name', 'required' => true}], options[:options])
327
+ payload['userSource']['name'] = v_prompt['name'] if v_prompt['name']
328
+ end
329
+
330
+ # custom options by type
331
+ my_option_types = load_user_source_type_option_types(user_source_type['type'])
332
+ v_prompt = Morpheus::Cli::OptionTypes.prompt(my_option_types, options[:options])
333
+ payload['userSource'].deep_merge!(v_prompt)
334
+
335
+ # Default Account Role
336
+ # todo: a proper select
337
+ if !default_role_id
338
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => 'defaultAccountRole', 'fieldName' => 'id', 'type' => 'text', 'fieldLabel' => 'Default Account Role ID', 'required' => true}], options[:options])
339
+ if v_prompt['defaultAccountRole'] && v_prompt['defaultAccountRole']['id']
340
+ default_role_id = v_prompt['defaultAccountRole']['id']
341
+ end
342
+ end
343
+ if default_role_id
344
+ payload['userSource']['defaultAccountRole'] = {'id' => default_role_id }
345
+ end
346
+
347
+
348
+ if role_mappings
349
+ payload['roleMappings'] = role_mappings
350
+ end
351
+
352
+ if role_mapping_names
353
+ payload['roleMappingNames'] = role_mapping_names
354
+ end
355
+
356
+ # support old -O options
357
+ payload['userSource'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
358
+
359
+
360
+ end
361
+ # dry run?
362
+ if options[:dry_run]
363
+ print_dry_run @user_sources_interface.dry.create(account_id, payload)
364
+ return
365
+ end
366
+ # do it
367
+ json_response = @user_sources_interface.create(account_id, payload)
368
+ # print and return result
369
+ if options[:json]
370
+ puts as_json(json_response, options)
371
+ return 0
372
+ end
373
+ user_source = json_response['userSource']
374
+ print_green_success "Added User Source #{user_source['name']}"
375
+ get([user_source['id']])
376
+ return 0
377
+ rescue RestClient::Exception => e
378
+ print_rest_exception(e, options)
379
+ exit 1
380
+ end
381
+ end
382
+
383
+ def update(args)
384
+ options = {}
385
+ params = {}
386
+ account_id = nil
387
+ role_mappings = nil
388
+ role_mapping_names = nil
389
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
390
+ opts.banner = subcommand_usage("[name] [options]")
391
+ opts.on('--name VALUE', String, "Name for this user source") do |val|
392
+ params['name'] = val
393
+ end
394
+ opts.on('--description VALUE', String, "Description") do |val|
395
+ params['description'] = val
396
+ end
397
+ opts.on('--role-mappings MAPPINGS', String, "Role Mappings in the format id1:FQN,id2:FQN2") do |val|
398
+ role_mappings = {}
399
+ val.split(',').collect {|it| it.strip.split(':') }.each do |pair|
400
+ k, v = pair[0], pair[1]
401
+ if !k.to_s.empty?
402
+ role_mappings[k.to_s] = v
403
+ end
404
+ end
405
+ end
406
+ opts.on('--role-mapping-names MAPPINGS', String, "Role Mapping Names in the format id1:Name1,id2:Name2") do |val|
407
+ role_mapping_names = {}
408
+ val.split(',').collect {|it| it.strip.split(':') }.each do |pair|
409
+ k, v = pair[0], pair[1]
410
+ if !k.to_s.empty?
411
+ role_mapping_names[k.to_s] = v
412
+ end
413
+ end
414
+ end
415
+ build_common_options(opts, options, [:options, :json, :dry_run, :remote])
416
+ opts.footer = "Update a user source." + "\n" +
417
+ "[name] is required. This is the name or id of a user source."
418
+ end
419
+ optparse.parse!(args)
420
+ if args.count < 1
421
+ puts optparse
422
+ exit 1
423
+ end
424
+ connect(options)
425
+ begin
426
+ user_source = find_user_source_by_name_or_id(nil, args[0])
427
+ exit 1 if user_source.nil?
428
+ payload = nil
429
+ if options[:payload]
430
+ payload = options[:payload]
431
+ else
432
+ payload = {'userSource' => {}}
433
+
434
+ # Name
435
+ if params['name']
436
+ payload['userSource']['name'] = params['name']
437
+ end
438
+
439
+ # Description
440
+ if params['description']
441
+ payload['userSource']['description'] = params['description']
442
+ end
443
+
444
+ if role_mappings
445
+ payload['roleMappings'] = role_mappings
446
+ end
447
+
448
+ if role_mapping_names
449
+ payload['roleMappingNames'] = role_mapping_names
450
+ end
451
+
452
+ # support old -O options
453
+ payload['userSource'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
454
+
455
+ end
456
+
457
+ if options[:dry_run]
458
+ print_dry_run @user_sources_interface.dry.update(nil, user_source['id'], payload)
459
+ return
460
+ end
461
+
462
+ json_response = @user_sources_interface.update(nil, user_source['id'], payload)
463
+
464
+ if options[:json]
465
+ puts JSON.pretty_generate(json_response)
466
+ return
467
+ end
468
+
469
+ print_green_success "Updated User Source #{params['name'] || user_source['name']}"
470
+ get([user_source['id']])
471
+ rescue RestClient::Exception => e
472
+ print_rest_exception(e, options)
473
+ exit 1
474
+ end
475
+ end
476
+
477
+ def activate(args)
478
+ options = {}
479
+ params = {}
480
+ account_id = nil
481
+ role_mappings = nil
482
+ role_mapping_names = nil
483
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
484
+ opts.banner = subcommand_usage("[name]")
485
+ build_common_options(opts, options, [:options, :json, :dry_run, :remote])
486
+ opts.footer = "Activate a user source." + "\n" +
487
+ "[name] is required. This is the name or id of a user source."
488
+ end
489
+ optparse.parse!(args)
490
+ if args.count < 1
491
+ puts optparse
492
+ exit 1
493
+ end
494
+ connect(options)
495
+ begin
496
+ user_source = find_user_source_by_name_or_id(nil, args[0])
497
+ exit 1 if user_source.nil?
498
+ payload = nil
499
+ if options[:payload]
500
+ payload = options[:payload]
501
+ else
502
+ payload = {}
503
+ # support old -O options
504
+ payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
505
+ end
506
+
507
+ if options[:dry_run]
508
+ print_dry_run @user_sources_interface.dry.activate(nil, user_source['id'], payload)
509
+ return
510
+ end
511
+
512
+ json_response = @user_sources_interface.activate(nil, user_source['id'], payload)
513
+
514
+ if options[:json]
515
+ puts JSON.pretty_generate(json_response)
516
+ return
517
+ end
518
+
519
+ print_green_success "Activated User Source #{user_source['name']}"
520
+ get([user_source['id']])
521
+ rescue RestClient::Exception => e
522
+ print_rest_exception(e, options)
523
+ exit 1
524
+ end
525
+ end
526
+
527
+ def deactivate(args)
528
+ options = {}
529
+ params = {}
530
+ account_id = nil
531
+ role_mappings = nil
532
+ role_mapping_names = nil
533
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
534
+ opts.banner = subcommand_usage("[name]")
535
+ build_common_options(opts, options, [:options, :json, :dry_run, :remote])
536
+ opts.footer = "Deactivate a user source." + "\n" +
537
+ "[name] is required. This is the name or id of a user source."
538
+ end
539
+ optparse.parse!(args)
540
+ if args.count < 1
541
+ puts optparse
542
+ exit 1
543
+ end
544
+ connect(options)
545
+ begin
546
+ user_source = find_user_source_by_name_or_id(nil, args[0])
547
+ exit 1 if user_source.nil?
548
+ payload = nil
549
+ if options[:payload]
550
+ payload = options[:payload]
551
+ else
552
+ payload = {}
553
+ # support old -O options
554
+ payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
555
+ end
556
+
557
+ if options[:dry_run]
558
+ print_dry_run @user_sources_interface.dry.deactivate(nil, user_source['id'], payload)
559
+ return
560
+ end
561
+
562
+ json_response = @user_sources_interface.deactivate(nil, user_source['id'], payload)
563
+
564
+ if options[:json]
565
+ puts JSON.pretty_generate(json_response)
566
+ return
567
+ end
568
+
569
+ print_green_success "Activated User Source #{user_source['name']}"
570
+ get([user_source['id']])
571
+ rescue RestClient::Exception => e
572
+ print_rest_exception(e, options)
573
+ exit 1
574
+ end
575
+ end
576
+
577
+ def update_subdomain(args)
578
+ options = {}
579
+ params = {}
580
+ account_id = nil
581
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
582
+ opts.banner = subcommand_usage("[name]")
583
+ opts.on('--subdomain VALUE', String, "New subdomain for this user source") do |val|
584
+ params['subdomain'] = (val == 'null') ? nil : val
585
+ end
586
+ build_common_options(opts, options, [:options, :json, :dry_run, :remote])
587
+ opts.footer = "Update subdomain for a user source." + "\n" +
588
+ "[name] is required. This is the name or id of a user source."
589
+ end
590
+ optparse.parse!(args)
591
+ if args.count < 1
592
+ puts optparse
593
+ exit 1
594
+ end
595
+ connect(options)
596
+ begin
597
+ user_source = find_user_source_by_name_or_id(nil, args[0])
598
+ exit 1 if user_source.nil?
599
+ payload = nil
600
+ if options[:payload]
601
+ payload = options[:payload]
602
+ else
603
+ payload = {}
604
+ payload['subdomain'] = params['subdomain'] if params.key?('subdomain')
605
+ # support old -O options
606
+ payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
607
+ end
608
+
609
+ if options[:dry_run]
610
+ print_dry_run @user_sources_interface.dry.update_subdomain(nil, user_source['id'], payload)
611
+ return
612
+ end
613
+
614
+ json_response = @user_sources_interface.update_subdomain(nil, user_source['id'], payload)
615
+
616
+ if options[:json]
617
+ puts JSON.pretty_generate(json_response)
618
+ return
619
+ end
620
+
621
+ print_green_success "Activated User Source #{user_source['name']}"
622
+ get([user_source['id']])
623
+ rescue RestClient::Exception => e
624
+ print_rest_exception(e, options)
625
+ exit 1
626
+ end
627
+ end
628
+
629
+ def remove(args)
630
+ options = {}
631
+ optparse = Morpheus::Cli::OptionParser.new do|opts|
632
+ opts.banner = subcommand_usage("[name]")
633
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :remote])
634
+ opts.footer = "Delete a user_source."
635
+ end
636
+ optparse.parse!(args)
637
+ if args.count < 1
638
+ puts optparse
639
+ exit 1
640
+ end
641
+ connect(options)
642
+
643
+ begin
644
+ user_source = find_user_source_by_name_or_id(nil, args[0])
645
+ exit 1 if user_source.nil?
646
+
647
+ unless Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the user source #{user_source['name']}?", options)
648
+ exit
649
+ end
650
+ if options[:dry_run]
651
+ print_dry_run @user_sources_interface.dry.destroy(nil, user_source['id'])
652
+ return
653
+ end
654
+ json_response = @user_sources_interface.destroy(nil, user_source['id'])
655
+
656
+ if options[:json]
657
+ print JSON.pretty_generate(json_response), "\n"
658
+ return
659
+ end
660
+
661
+ print_green_success "Removed User Source #{user_source['name']}"
662
+ #list([])
663
+ rescue RestClient::Exception => e
664
+ print_rest_exception(e, options)
665
+ exit 1
666
+ end
667
+ end
668
+
669
+ def list_types(args)
670
+ options = {}
671
+ params = {}
672
+ account = nil
673
+ account_id = nil
674
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
675
+ opts.banner = subcommand_usage()
676
+ build_common_options(opts, options, [:list, :json, :yaml, :csv, :fields, :dry_run, :remote])
677
+ opts.footer = "List user source types."
678
+ end
679
+ optparse.parse!(args)
680
+ connect(options)
681
+ # instance is required right now.
682
+ # account_id = args[0] if !account_id
683
+ if args.count != 0
684
+ print_error Morpheus::Terminal.angry_prompt
685
+ puts_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args.inspect}\n#{optparse}"
686
+ return 1
687
+ end
688
+ begin
689
+ # construct payload
690
+ params.merge!(parse_list_options(options))
691
+
692
+ if options[:dry_run]
693
+ print_dry_run @user_sources_interface.dry.list_types(params)
694
+ return
695
+ end
696
+
697
+ json_response = @user_sources_interface.list_types(params)
698
+ if options[:include_fields]
699
+ json_response = {"userSourceTypes" => filter_data(json_response["userSourceTypes"], options[:include_fields]) }
700
+ end
701
+ if options[:json]
702
+ puts as_json(json_response, options)
703
+ return 0
704
+ elsif options[:csv]
705
+ puts records_as_csv(json_response['userSourceTypes'], options)
706
+ return 0
707
+ elsif options[:yaml]
708
+ puts as_yaml(json_response, options)
709
+ return 0
710
+ end
711
+ user_source_types = json_response['userSourceTypes']
712
+ title = "Morpheus User Source Types"
713
+ subtitles = []
714
+ subtitles += parse_list_subtitles(options)
715
+ print_h1 title, subtitles
716
+ if user_source_types.empty?
717
+ print cyan,"No types found.",reset,"\n"
718
+ else
719
+ print_user_source_types_table(user_source_types, options)
720
+ print_results_pagination(json_response, {:label => "type", :n_label => "types"})
721
+ end
722
+ print reset,"\n"
723
+ rescue RestClient::Exception => e
724
+ print_rest_exception(e, options)
725
+ return 1
726
+ end
727
+ end
728
+
729
+ def get_type(args)
730
+ options = {}
731
+ params = {}
732
+ account = nil
733
+ account_id = nil
734
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
735
+ opts.banner = subcommand_usage("[type]")
736
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
737
+ opts.footer = "Get details about a user source type." + "\n" +
738
+ "[type] is required. This is the type identifier."
739
+ end
740
+ optparse.parse!(args)
741
+ connect(options)
742
+ # instance is required right now.
743
+ # account_id = args[0] if !account_id
744
+ expected_arg_count = 1
745
+ if args.count != expected_arg_count
746
+ print_error Morpheus::Terminal.angry_prompt
747
+ puts_error "wrong number of arguments, expected #{expected_arg_count} and got (#{args.count}) #{args.inspect}\n#{optparse}"
748
+ return 1
749
+ end
750
+ begin
751
+ user_source_type_id = args[0]
752
+
753
+ # all_user_source_types = @user_sources_interface.dry.list_types({})['userSourceTypes']
754
+ # user_source_type = all_user_source_types.find {|it| it['type'] == user_source_type_id }
755
+ # if !user_source_type
756
+ # print_red_alert "User Source Type not found by id '#{user_source_type_id}'"
757
+ # return 1
758
+ # end
759
+
760
+ # construct payload
761
+
762
+ if options[:dry_run]
763
+ print_dry_run @user_sources_interface.dry.list_types(user_source_type_id, params)
764
+ return
765
+ end
766
+ json_response = @user_sources_interface.get_type(user_source_type_id, params)
767
+ user_source_type = json_response['userSourceType']
768
+ if options[:include_fields]
769
+ json_response = {"userSource" => filter_data(user_source_type, options[:include_fields]) }
770
+ end
771
+ if options[:json]
772
+ puts as_json(json_response, options)
773
+ return 0
774
+ elsif options[:yaml]
775
+ puts as_yaml(json_response, options)
776
+ return 0
777
+ elsif options[:csv]
778
+ puts records_as_csv([user_source_type], options)
779
+ return 0
780
+ end
781
+ title = "User Source Type"
782
+ subtitles = []
783
+ print_h1 title, subtitles
784
+ print cyan
785
+ description_cols = {
786
+ #"ID" => lambda {|it| it['id'] },
787
+ # "Name" => lambda {|it| it['name'] },
788
+ # "Code" => lambda {|it| it['code'] },
789
+ "Type" => lambda {|it| it['type'] },
790
+ "External Login" => lambda {|it| format_boolean it['externalLogin'] },
791
+ "Selectable" => lambda {|it| format_boolean it['userSelectable'] },
792
+ }
793
+ print_description_list(description_cols, user_source_type)
794
+
795
+ # show config settings...
796
+ my_option_types = user_source_type['optionTypes']
797
+
798
+
799
+ if !my_option_types
800
+ my_option_types = load_user_source_type_option_types(user_source_type['type'])
801
+ end
802
+
803
+ print_h2 "Configuration Option Types"
804
+ if my_option_types && my_option_types.size > 0
805
+ columns = [
806
+ {"FIELD LABEL" => lambda {|it| it['fieldLabel'] } },
807
+ {"FIELD NAME" => lambda {|it| [it['fieldContext'], it['fieldName']].select {|it| !it.to_s.empty? }.join('.') } },
808
+ {"TYPE" => lambda {|it| it['type'] } },
809
+ {"DEFAULT" => lambda {|it| it['defaultValue'] } },
810
+ {"REQUIRED" => lambda {|it| format_boolean it['required'] } },
811
+ ]
812
+ print as_pretty_table(my_option_types, columns)
813
+ else
814
+ print yellow,"No option types found.","\n",reset
815
+ end
816
+
817
+ print reset,"\n"
818
+ rescue RestClient::Exception => e
819
+ print_rest_exception(e, options)
820
+ return 1
821
+ end
822
+ end
823
+
824
+ private
825
+
826
+ def find_user_source_by_name_or_id(account_id, val)
827
+ if val.to_s =~ /\A\d{1,}\Z/
828
+ return find_user_source_by_id(account_id, val)
829
+ else
830
+ return find_user_source_by_name(account_id, val)
831
+ end
832
+ end
833
+
834
+ def find_user_source_by_id(account_id, id)
835
+ begin
836
+ json_response = @user_sources_interface.get(account_id, id.to_i)
837
+ return json_response['userSource']
838
+ rescue RestClient::Exception => e
839
+ if e.response && e.response.code == 404
840
+ print_red_alert "User Source not found by id #{id}"
841
+ else
842
+ raise e
843
+ end
844
+ end
845
+ end
846
+
847
+ def find_user_source_by_name(account_id, name)
848
+ user_sources = @user_sources_interface.list(account_id, {name: name.to_s})['userSources']
849
+ if user_sources.empty?
850
+ print_red_alert "User Source not found by name #{name}"
851
+ return nil
852
+ elsif user_sources.size > 1
853
+ print_red_alert "#{user_sources.size} user sources found by name #{name}"
854
+ print_user_sources_table(user_sources, {color: red})
855
+ print_red_alert "Try using ID instead"
856
+ print reset,"\n"
857
+ return nil
858
+ else
859
+ return user_sources[0]
860
+ end
861
+ end
862
+
863
+ def print_user_sources_table(user_sources, opts={})
864
+ columns = [
865
+ {"ID" => lambda {|user_source| user_source['id'] } },
866
+ {"NAME" => lambda {|user_source| user_source['name'] } },
867
+ {"TYPE" => lambda {|user_source| user_source['type'] } },
868
+ {"ACCOUNT" => lambda {|user_source| user_source['account'] ? user_source['account']['name'] : '' } },
869
+ {"ACTIVE" => lambda {|user_source| format_boolean user_source['active'] } },
870
+ ]
871
+ if opts[:include_fields]
872
+ columns = opts[:include_fields]
873
+ end
874
+ print as_pretty_table(user_sources, columns, opts)
875
+ end
876
+
877
+ def print_user_source_types_table(user_sources, opts={})
878
+ columns = [
879
+ {"TYPE" => lambda {|user_source| user_source['type'] } },
880
+ # {"NAME" => lambda {|user_source| user_source['name'] } },
881
+ ]
882
+ if opts[:include_fields]
883
+ columns = opts[:include_fields]
884
+ end
885
+ print as_pretty_table(user_sources, columns, opts)
886
+ end
887
+
888
+ # manual options until this is data driven
889
+ # * ldap [ldap]
890
+ # * jumpCloud [jumpCloud]
891
+ # * activeDirectory [activeDirectory]
892
+ # * okta [okta]
893
+ # * oneLogin [oneLogin]
894
+ # * saml [saml]
895
+ # * customExternal [customExternal]
896
+ # * customApi [customApi]
897
+ def load_user_source_type_option_types(type_code)
898
+ if type_code == 'ldap'
899
+ [
900
+ {'fieldContext' => 'config', 'fieldName' => 'url', 'type' => 'text', 'fieldLabel' => 'URL', 'required' => true, 'description' => ''},
901
+ {'fieldContext' => 'config', 'fieldName' => 'bindingUsername', 'type' => 'text', 'fieldLabel' => 'Binding Username', 'required' => true, 'description' => ''},
902
+ {'fieldContext' => 'config', 'fieldName' => 'bindingPassword', 'type' => 'password', 'fieldLabel' => 'Binding Password', 'required' => true, 'description' => ''},
903
+ {'fieldContext' => 'config', 'fieldName' => 'requiredGroup', 'type' => 'text', 'fieldLabel' => 'Required group name (a.k.a. tag)', 'required' => true, 'description' => ''},
904
+ ]
905
+ elsif type_code == 'jumpCloud'
906
+ [
907
+ {'fieldContext' => 'config', 'fieldName' => 'organizationId', 'type' => 'text', 'fieldLabel' => 'Organization ID', 'required' => true, 'description' => ''},
908
+ {'fieldContext' => 'config', 'fieldName' => 'bindingUsername', 'type' => 'text', 'fieldLabel' => 'Binding Username', 'required' => true, 'description' => ''},
909
+ {'fieldContext' => 'config', 'fieldName' => 'bindingPassword', 'type' => 'password', 'fieldLabel' => 'Binding Password', 'required' => true, 'description' => ''},
910
+ {'fieldContext' => 'config', 'fieldName' => 'requiredRole', 'type' => 'text', 'fieldLabel' => 'Required group name (a.k.a. tag)', 'required' => true, 'description' => ''},
911
+ ]
912
+ elsif type_code == 'activeDirectory'
913
+ [
914
+ {'fieldContext' => 'config', 'fieldName' => 'url', 'type' => 'text', 'fieldLabel' => 'AD Server', 'required' => true, 'description' => ''},
915
+ {'fieldContext' => 'config', 'fieldName' => 'domain', 'type' => 'text', 'fieldLabel' => 'Domain', 'required' => true, 'description' => ''},
916
+ {'fieldContext' => 'config', 'fieldName' => 'useSSL', 'type' => 'checkbox', 'fieldLabel' => 'Use SSL', 'required' => true, 'description' => '', 'defaultValue' => false},
917
+ {'fieldContext' => 'config', 'fieldName' => 'bindingUsername', 'type' => 'text', 'fieldLabel' => 'Binding Username', 'required' => true, 'description' => ''},
918
+ {'fieldContext' => 'config', 'fieldName' => 'bindingPassword', 'type' => 'password', 'fieldLabel' => 'Binding Password', 'required' => true, 'description' => ''},
919
+ {'fieldContext' => 'config', 'fieldName' => 'requiredGroup', 'type' => 'text', 'fieldLabel' => 'Required Group', 'required' => true, 'description' => ''},
920
+ {'fieldContext' => 'config', 'fieldName' => 'searchMemberGroups', 'type' => 'checkbox', 'fieldLabel' => 'Include Member Groups', 'required' => true, 'description' => '', 'defaultValue' => false},
921
+ ]
922
+ elsif type_code == 'okta'
923
+ [
924
+ {'fieldContext' => 'config', 'fieldName' => 'url', 'type' => 'text', 'fieldLabel' => 'OKTA URL', 'required' => true, 'description' => ''},
925
+ {'fieldContext' => 'config', 'fieldName' => 'administratorAPIToken', 'type' => 'password', 'fieldLabel' => 'Adminstrator API Token', 'required' => true, 'description' => ''},
926
+ {'fieldContext' => 'config', 'fieldName' => 'requiredGroup', 'type' => 'text', 'fieldLabel' => 'Required Group', 'required' => true, 'description' => ''}
927
+ ]
928
+ elsif type_code == 'oneLogin'
929
+ [
930
+ {'fieldContext' => 'config', 'fieldName' => 'subdomain', 'type' => 'text', 'fieldLabel' => 'OneLogin Subdomain', 'required' => true, 'description' => ''},
931
+ {'fieldContext' => 'config', 'fieldName' => 'region', 'type' => 'text', 'fieldLabel' => 'OneLogin Region', 'required' => true, 'description' => ''},
932
+ {'fieldContext' => 'config', 'fieldName' => 'clientSecret', 'type' => 'password', 'fieldLabel' => 'API Client Secret', 'required' => true, 'description' => ''},
933
+ {'fieldContext' => 'config', 'fieldName' => 'clientId', 'type' => 'text', 'fieldLabel' => 'API Client ID', 'required' => true, 'description' => ''},
934
+ {'fieldContext' => 'config', 'fieldName' => 'requiredRole', 'type' => 'text', 'fieldLabel' => 'Required Role', 'required' => true, 'description' => ''},
935
+ ]
936
+ elsif type_code == 'saml'
937
+ [
938
+ {'fieldContext' => 'config', 'fieldName' => 'url', 'type' => 'text', 'fieldLabel' => 'Login Redirect URL', 'required' => true, 'description' => ''},
939
+ {'fieldContext' => 'config', 'fieldName' => 'doNotIncludeSAMLRequest', 'type' => 'checkbox', 'fieldLabel' => 'Exclude SAMLRequest Parameter', 'required' => true, 'description' => 'Do not include SAMLRequest parameter', 'defaultValue' => false},
940
+ {'fieldContext' => 'config', 'fieldName' => 'logoutUrl', 'type' => 'text', 'fieldLabel' => 'Logout Post URL', 'required' => true, 'description' => ''},
941
+ {'fieldContext' => 'config', 'fieldName' => 'publicKey', 'type' => 'textarea', 'fieldLabel' => 'Signing Public Key', 'required' => true, 'description' => ''},
942
+ ]
943
+ elsif type_code == 'customExternal'
944
+ [
945
+ {'fieldContext' => 'config', 'fieldName' => 'loginUrl', 'type' => 'text', 'fieldLabel' => 'External Login URL', 'required' => true, 'description' => ''},
946
+ {'fieldContext' => 'config', 'fieldName' => 'doNotIncludeSAMLRequest', 'type' => 'checkbox', 'fieldLabel' => 'Exclude SAMLRequest Parameter', 'required' => true, 'description' => 'Do not include SAMLRequest parameter', 'defaultValue' => false},
947
+ {'fieldContext' => 'config', 'fieldName' => 'logout', 'type' => 'text', 'fieldLabel' => 'External Logout URL', 'required' => true, 'description' => ''},
948
+ {'fieldContext' => 'config', 'fieldName' => 'encryptionAlgo', 'type' => 'select', 'selectOptions' => ['NONE','AES','DES','DESede','HmacSHA1', 'HmacSHA256'].collect {|it| { 'name' => it, 'value' => it} }, 'fieldLabel' => 'Encryption Algorithm', 'required' => true, 'description' => ''},
949
+ {'fieldContext' => 'config', 'fieldName' => 'encryptionKey', 'type' => 'text', 'fieldLabel' => 'Encryption Key', 'required' => true, 'description' => ''},
950
+ ]
951
+ elsif type_code == 'customApi'
952
+ [
953
+ {'fieldContext' => 'config', 'fieldName' => 'endpoint', 'type' => 'text', 'fieldLabel' => 'API Endpoint', 'required' => true, 'description' => ''},
954
+ {'fieldContext' => 'config', 'fieldName' => 'apiStyle', 'type' => 'select', 'selectOptions' => ['Form URL Encoded [GET]','Form URL Encoded [POST]','JSON [POST]','XML [POST]','HTTP Basic [GET]'].collect {|it| { 'name' => it, 'value' => it} }, 'fieldLabel' => 'API Style', 'required' => true, 'description' => ''},
955
+ {'fieldContext' => 'config', 'fieldName' => 'encryptionAlgo', 'type' => 'select', 'selectOptions' => ['NONE','AES','DES','DESede','HmacSHA1', 'HmacSHA256'].collect {|it| { 'name' => it, 'value' => it} }, 'fieldLabel' => 'Encryption Algorithm', 'required' => true, 'description' => ''},
956
+ {'fieldContext' => 'config', 'fieldName' => 'encryptionKey', 'type' => 'text', 'fieldLabel' => 'Encryption Key', 'required' => true, 'description' => ''},
957
+ ]
958
+ else
959
+ print "unknown user source type: #{type_code}"
960
+ []
961
+ end
962
+ end
963
+ end