morpheus-cli 3.1.1 → 3.1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,602 @@
1
+ require 'rest_client'
2
+ require 'optparse'
3
+ require 'filesize'
4
+ require 'table_print'
5
+ require 'morpheus/cli/cli_command'
6
+ require 'morpheus/cli/mixins/infrastructure_helper'
7
+
8
+ class Morpheus::Cli::NetworkGroupsCommand
9
+ include Morpheus::Cli::CliCommand
10
+ include Morpheus::Cli::InfrastructureHelper
11
+
12
+ set_command_name :'network-groups'
13
+
14
+ register_subcommands :list, :get, :add, :update, :remove
15
+
16
+ # set_default_subcommand :list
17
+
18
+ def initialize()
19
+ # @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
20
+ end
21
+
22
+ def connect(opts)
23
+ @api_client = establish_remote_appliance_connection(opts)
24
+ @network_groups_interface = @api_client.network_groups
25
+ @clouds_interface = @api_client.clouds
26
+ @options_interface = @api_client.options
27
+ end
28
+
29
+ def handle(args)
30
+ handle_subcommand(args)
31
+ end
32
+
33
+ def list(args)
34
+ options = {}
35
+ params = {}
36
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
37
+ opts.banner = subcommand_usage()
38
+ build_common_options(opts, options, [:list, :json, :yaml, :csv, :fields, :json, :dry_run, :remote])
39
+ opts.footer = "List network groups."
40
+ end
41
+ optparse.parse!(args)
42
+ connect(options)
43
+ begin
44
+ [:phrase, :offset, :max, :sort, :direction].each do |k|
45
+ params[k] = options[k] unless options[k].nil?
46
+ end
47
+ if options[:dry_run]
48
+ print_dry_run @network_groups_interface.dry.list(params)
49
+ return
50
+ end
51
+ json_response = @network_groups_interface.list(params)
52
+ network_groups = json_response["networkGroups"]
53
+ if options[:include_fields]
54
+ json_response = {"networkGroups" => filter_data(network_groups, options[:include_fields]) }
55
+ end
56
+ if options[:json]
57
+ puts as_json(json_response, options)
58
+ return 0
59
+ elsif options[:yaml]
60
+ puts as_yaml(json_response, options)
61
+ return 0
62
+ elsif options[:csv]
63
+ puts records_as_csv(network_groups, options)
64
+ return 0
65
+ end
66
+ title = "Morpheus Network Groups"
67
+ subtitles = []
68
+ if params[:phrase]
69
+ subtitles << "Search: #{params[:phrase]}".strip
70
+ end
71
+ print_h1 title, subtitles
72
+ if network_groups.empty?
73
+ print cyan,"No network groups found.",reset,"\n"
74
+ else
75
+ rows = network_groups.collect {|network_group|
76
+ row = {
77
+ id: network_group['id'],
78
+ name: network_group['name'],
79
+ description: network_group['description'],
80
+ # networks: network_group['networks'] ? network_group['networks'].collect {|it| it['name'] }.uniq.join(', ') : '',
81
+ networks: network_group['networks'] ? network_group['networks'].size : 0,
82
+ visibility: network_group['visibility'].to_s.capitalize,
83
+ tenants: network_group['tenants'] ? network_group['tenants'].collect {|it| it['name'] }.uniq.join(', ') : ''
84
+ }
85
+ row
86
+ }
87
+ columns = [:id, :name, :description, :networks, :visibility, :tenants]
88
+ if options[:include_fields]
89
+ columns = options[:include_fields]
90
+ end
91
+ print cyan
92
+ print as_pretty_table(rows, columns, options)
93
+ print reset
94
+ print_results_pagination(json_response, {:label => "network group", :n_label => "network groups"})
95
+ end
96
+ print reset,"\n"
97
+ return 0
98
+ rescue RestClient::Exception => e
99
+ print_rest_exception(e, options)
100
+ exit 1
101
+ end
102
+ end
103
+
104
+ def get(args)
105
+ options = {}
106
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
107
+ opts.banner = subcommand_usage("[network-group]")
108
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
109
+ opts.footer = "Get details about a network group." + "\n" +
110
+ "[network-group] is required. This is the name or id of a network group."
111
+ end
112
+ optparse.parse!(args)
113
+ if args.count != 1
114
+ print_error Morpheus::Terminal.angry_prompt
115
+ puts_error "#{command_name} missing argument: [network-group]\n#{optparse}"
116
+ return 1
117
+ end
118
+ connect(options)
119
+ begin
120
+ if options[:dry_run]
121
+ if args[0].to_s =~ /\A\d{1,}\Z/
122
+ print_dry_run @network_groups_interface.dry.get(args[0].to_i)
123
+ else
124
+ print_dry_run @network_groups_interface.dry.list({name:args[0]})
125
+ end
126
+ return
127
+ end
128
+ network_group = find_network_group_by_name_or_id(args[0])
129
+ return 1 if network_group.nil?
130
+ json_response = {'networkGroup' => network_group} # skip redundant request
131
+ # json_response = @network_groups_interface.get(network_group['id'])
132
+ network_group = json_response['networkGroup']
133
+ if options[:include_fields]
134
+ json_response = {'networkGroup' => filter_data(network_group, options[:include_fields]) }
135
+ end
136
+ if options[:json]
137
+ puts as_json(json_response, options)
138
+ return 0
139
+ elsif options[:yaml]
140
+ puts as_yaml(json_response, options)
141
+ return 0
142
+ elsif options[:csv]
143
+ puts records_as_csv([network_group], options)
144
+ return 0
145
+ end
146
+ print_h1 "Network Group Details"
147
+ print cyan
148
+ description_cols = {
149
+ "ID" => 'id',
150
+ "Name" => 'name',
151
+ "Description" => 'description',
152
+ "Networks" => lambda {|it| it['networks'] ? it['networks'].collect {|it| it['name'] }.uniq.join(', ') : '' },
153
+ "Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
154
+ "Tenants" => lambda {|it| it['tenants'] ? it['tenants'].collect {|it| it['name'] }.uniq.join(', ') : '' },
155
+ # "Owner" => lambda {|it| it['owner'] ? it['owner']['name'] : '' },
156
+ }
157
+ print_description_list(description_cols, network_group)
158
+
159
+
160
+ if network_group['resourcePermission'].nil?
161
+ print "\n", "No group access found", "\n"
162
+ else
163
+ print_h2 "Group Access"
164
+ rows = []
165
+ if network_group['resourcePermission']['all']
166
+ rows.push({"name" => 'All'})
167
+ end
168
+ if network_group['resourcePermission']['sites']
169
+ network_group['resourcePermission']['sites'].each do |site|
170
+ rows.push(site)
171
+ end
172
+ end
173
+ rows = rows.collect do |site|
174
+ {group: site['name'], default: site['default'] ? 'Yes' : ''}
175
+ end
176
+ columns = [:group, :default]
177
+ print cyan
178
+ print as_pretty_table(rows, columns)
179
+ end
180
+
181
+ print reset,"\n"
182
+ return 0
183
+ rescue RestClient::Exception => e
184
+ print_rest_exception(e, options)
185
+ return 1
186
+ end
187
+ end
188
+
189
+ def add(args)
190
+ options = {}
191
+ tenants = nil
192
+ group_access_all = nil
193
+ group_access_list = nil
194
+ group_defaults_list = nil
195
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
196
+ opts.banner = subcommand_usage("--networks [id,id,id]")
197
+ opts.on('--name VALUE', String, "Name for this network group") do |val|
198
+ options['name'] = val
199
+ end
200
+ opts.on('--description VALUE', String, "Description of network group") do |val|
201
+ options['description'] = val
202
+ end
203
+ opts.on('--networks LIST', Array, "Networks in the group, comma separated list of network IDs") do |list|
204
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
205
+ options['networks'] = []
206
+ else
207
+ options['networks'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
208
+ end
209
+ end
210
+ opts.on('--group-access-all [on|off]', String, "Toggle Access for all groups.") do |val|
211
+ group_access_all = val.to_s == 'on' || val.to_s == 'true'
212
+ end
213
+ opts.on('--group-access LIST', Array, "Group Access, comma separated list of group IDs.") do |list|
214
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
215
+ group_access_list = []
216
+ else
217
+ group_access_list = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
218
+ end
219
+ end
220
+ opts.on('--group-defaults LIST', Array, "Group Default Selection, comma separated list of group IDs") do |list|
221
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
222
+ group_defaults_list = []
223
+ else
224
+ group_defaults_list = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
225
+ end
226
+ end
227
+ opts.on('--tenants LIST', Array, "Tenant Access, comma separated list of account IDs") do |list|
228
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
229
+ options['tenants'] = []
230
+ else
231
+ options['tenants'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
232
+ end
233
+ end
234
+ opts.on('--accounts LIST', Array, "alias for --tenants") do |list|
235
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
236
+ options['tenants'] = []
237
+ else
238
+ options['tenants'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
239
+ end
240
+ end
241
+ opts.on('--visibility [private|public]', String, "Visibility") do |val|
242
+ options['visibility'] = val
243
+ end
244
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
245
+ opts.footer = "Create a new network group." + "\n" +
246
+ "[name] is required and can be passed as --name instead."
247
+ end
248
+ optparse.parse!(args)
249
+ if args.count > 1
250
+ print_error Morpheus::Terminal.angry_prompt
251
+ puts_error "wrong number of arguments, expected 0-1 and got #{args.count}\n#{optparse}"
252
+ return 1
253
+ end
254
+ connect(options)
255
+ begin
256
+ # merge -O options into normally parsed options
257
+ options.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
258
+
259
+ # support [name] as first argument
260
+ if args[0]
261
+ options['name'] = args[0]
262
+ end
263
+
264
+ # construct payload
265
+ payload = nil
266
+ if options[:payload]
267
+ payload = options[:payload]
268
+ else
269
+ # prompt for network options
270
+ payload = {
271
+ 'networkGroup' => {
272
+ # 'config' => {}
273
+ }
274
+ }
275
+
276
+ # allow arbitrary -O options
277
+ payload['networkGroup'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
278
+
279
+ # Name
280
+ if options['name']
281
+ payload['networkGroup']['name'] = options['name']
282
+ else
283
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Name for this network group.'}], options)
284
+ payload['networkGroup']['name'] = v_prompt['name']
285
+ end
286
+
287
+ # Description
288
+ if options['description']
289
+ payload['networkGroup']['description'] = options['description']
290
+ else
291
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'required' => false, 'description' => 'Description of network group.'}], options)
292
+ payload['networkGroup']['description'] = v_prompt['description']
293
+ end
294
+
295
+ # Networks
296
+ if options['networks']
297
+ payload['networkGroup']['networks'] = options['networks'].collect {|it| {id: it} }
298
+ else
299
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'networks', 'fieldLabel' => 'Networks', 'type' => 'text', 'required' => true, 'description' => 'Networks in the group, comma separated list of network IDs.'}], options)
300
+ payload['networkGroup']['networks'] = v_prompt['networks'].to_s.split(",").collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq.collect {|it| {id: it} }
301
+ end
302
+
303
+ # Group Access
304
+ if group_access_all != nil
305
+ payload['resourcePermissions'] ||= {}
306
+ payload['resourcePermissions']['all'] = group_access_all
307
+ end
308
+ if group_access_list != nil
309
+ payload['resourcePermissions'] ||= {}
310
+ payload['resourcePermissions']['sites'] = group_access_list.collect do |site_id|
311
+ site = {"id" => site_id.to_i}
312
+ if group_defaults_list && group_defaults_list.include?(site_id)
313
+ site["default"] = true
314
+ end
315
+ site
316
+ end
317
+ end
318
+
319
+ # Tenants
320
+ if options['tenants']
321
+ payload['tenantPermissions'] = {}
322
+ payload['tenantPermissions']['accounts'] = options['tenants']
323
+ end
324
+
325
+ # Visibility
326
+ if options['visibility'] != nil
327
+ payload['networkGroup']['visibility'] = options['visibility']
328
+ end
329
+
330
+ end
331
+
332
+
333
+ if options[:dry_run]
334
+ print_dry_run @network_groups_interface.dry.create(payload)
335
+ return
336
+ end
337
+ json_response = @network_groups_interface.create(payload)
338
+ if options[:json]
339
+ print JSON.pretty_generate(json_response)
340
+ print "\n"
341
+ elsif !options[:quiet]
342
+ network_group = json_response['networkGroup']
343
+ print_green_success "Added network group #{network_group['name']}"
344
+ get([network_group['id']])
345
+ end
346
+ return 0
347
+ rescue RestClient::Exception => e
348
+ print_rest_exception(e, options)
349
+ exit 1
350
+ end
351
+ end
352
+
353
+ def update(args)
354
+ options = {}
355
+ tenants = nil
356
+ group_access_all = nil
357
+ group_access_list = nil
358
+ group_defaults_list = nil
359
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
360
+ opts.banner = subcommand_usage("[network-group] [options]")
361
+ opts.on('--name VALUE', String, "Name for this network group") do |val|
362
+ options['name'] = val
363
+ end
364
+ opts.on('--description VALUE', String, "Description of network group") do |val|
365
+ options['description'] = val
366
+ end
367
+ opts.on('--networks LIST', Array, "Networks in the group, comma separated list of network IDs") do |list|
368
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
369
+ options['networks'] = []
370
+ else
371
+ options['networks'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
372
+ end
373
+ end
374
+ opts.on('--group-access-all [on|off]', String, "Toggle Access for all groups.") do |val|
375
+ group_access_all = val.to_s == 'on' || val.to_s == 'true'
376
+ end
377
+ opts.on('--group-access LIST', Array, "Group Access, comma separated list of group IDs.") do |list|
378
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
379
+ group_access_list = []
380
+ else
381
+ group_access_list = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
382
+ end
383
+ end
384
+ opts.on('--group-defaults LIST', Array, "Group Default Selection, comma separated list of group IDs") do |list|
385
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
386
+ group_defaults_list = []
387
+ else
388
+ group_defaults_list = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
389
+ end
390
+ end
391
+ opts.on('--tenants LIST', Array, "Tenant Access, comma separated list of account IDs") do |list|
392
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
393
+ options['tenants'] = []
394
+ else
395
+ options['tenants'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
396
+ end
397
+ end
398
+ opts.on('--accounts LIST', Array, "alias for --tenants") do |list|
399
+ if list.size == 1 && list[0] == 'null' # hacky way to clear it
400
+ options['tenants'] = []
401
+ else
402
+ options['tenants'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
403
+ end
404
+ end
405
+ opts.on('--visibility [private|public]', String, "Visibility") do |val|
406
+ options['visibility'] = val
407
+ end
408
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
409
+ opts.footer = "Update a network group." + "\n" +
410
+ "[network-group] is required. This is the id of a network group."
411
+ end
412
+ optparse.parse!(args)
413
+ if args.count != 1
414
+ print_error Morpheus::Terminal.angry_prompt
415
+ puts_error "wrong number of arguments, expected 1 and got #{args.count}\n#{optparse}"
416
+ return 1
417
+ end
418
+ connect(options)
419
+
420
+ begin
421
+ network_group = find_network_group_by_name_or_id(args[0])
422
+ return 1 if network_group.nil?
423
+
424
+ # merge -O options into normally parsed options
425
+ options.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
426
+
427
+ # construct payload
428
+ payload = nil
429
+ if options[:payload]
430
+ payload = options[:payload]
431
+ else
432
+ # prompt for network options
433
+ payload = {
434
+ 'networkGroup' => {
435
+ }
436
+ }
437
+
438
+ # allow arbitrary -O options
439
+ payload['networkGroup'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
440
+
441
+ # Name
442
+ if options['name']
443
+ payload['networkGroup']['name'] = options['name']
444
+ else
445
+ # v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Name for this network group.'}], options)
446
+ # payload['networkGroup']['name'] = v_prompt['name']
447
+ end
448
+
449
+ # Description
450
+ if options['description']
451
+ payload['networkGroup']['description'] = options['description']
452
+ else
453
+ # v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'required' => false, 'description' => 'Description of network group.'}], options)
454
+ # payload['networkGroup']['description'] = v_prompt['description']
455
+ end
456
+
457
+ # Networks
458
+ if options['networks']
459
+ payload['networkGroup']['networks'] = options['networks'].collect {|it| {id: it} }
460
+ else
461
+ # v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'networks', 'fieldLabel' => 'Networks', 'type' => 'text', 'required' => true, 'description' => 'Networks in the group, comma separated list of network IDs.'}], options)
462
+ # payload['networkGroup']['networks'] = v_prompt['networks'].to_s.split(",").collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq.collect {|it| {id: it} }
463
+ end
464
+
465
+ # Group Access
466
+ if group_access_all != nil
467
+ payload['resourcePermissions'] ||= {}
468
+ payload['resourcePermissions']['all'] = group_access_all
469
+ end
470
+ if group_access_list != nil
471
+ payload['resourcePermissions'] ||= {}
472
+ payload['resourcePermissions']['sites'] = group_access_list.collect do |site_id|
473
+ site = {"id" => site_id.to_i}
474
+ if group_defaults_list && group_defaults_list.include?(site_id)
475
+ site["default"] = true
476
+ end
477
+ site
478
+ end
479
+ end
480
+
481
+ # Tenants
482
+ if options['tenants']
483
+ payload['tenantPermissions'] = {}
484
+ payload['tenantPermissions']['accounts'] = options['tenants']
485
+ end
486
+
487
+ # Visibility
488
+ if options['visibility'] != nil
489
+ payload['networkGroup']['visibility'] = options['visibility']
490
+ end
491
+
492
+ end
493
+
494
+ if options[:dry_run]
495
+ print_dry_run @network_groups_interface.dry.update(network_group["id"], payload)
496
+ return
497
+ end
498
+ json_response = @network_groups_interface.update(network_group["id"], payload)
499
+ if options[:json]
500
+ puts as_json(json_response)
501
+ else
502
+ network_group = json_response['networkGroup']
503
+ print_green_success "Updated network group #{network_group['name']}"
504
+ get([network_group['id']])
505
+ end
506
+ return 0
507
+ rescue RestClient::Exception => e
508
+ print_rest_exception(e, options)
509
+ return 1
510
+ end
511
+ end
512
+
513
+ def remove(args)
514
+ options = {}
515
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
516
+ opts.banner = subcommand_usage("[network-group]")
517
+ build_common_options(opts, options, [:account, :auto_confirm, :json, :dry_run, :remote])
518
+ opts.footer = "Delete a network group." + "\n" +
519
+ "[network-group] is required. This is the name or id of a network group."
520
+ end
521
+ optparse.parse!(args)
522
+
523
+ if args.count < 1
524
+ print_error Morpheus::Terminal.angry_prompt
525
+ puts_error "#{command_name} missing argument: [network-group]\n#{optparse}"
526
+ return 1
527
+ end
528
+
529
+ connect(options)
530
+ begin
531
+ network_group = find_network_group_by_name_or_id(args[0])
532
+ return 1 if network_group.nil?
533
+
534
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the network group: #{network_group['name']}?")
535
+ return 9, "aborted command"
536
+ end
537
+ if options[:dry_run]
538
+ print_dry_run @network_groups_interface.dry.destroy(network_group['id'])
539
+ return 0
540
+ end
541
+ json_response = @network_groups_interface.destroy(network_group['id'])
542
+ if options[:json]
543
+ print JSON.pretty_generate(json_response)
544
+ print "\n"
545
+ else
546
+ print_green_success "Removed network group #{network_group['name']}"
547
+ # list([])
548
+ end
549
+ return 0
550
+ rescue RestClient::Exception => e
551
+ print_rest_exception(e, options)
552
+ return 1
553
+ end
554
+ end
555
+
556
+ private
557
+
558
+
559
+ def find_network_group_by_name_or_id(val)
560
+ if val.to_s =~ /\A\d{1,}\Z/
561
+ return find_network_group_by_id(val)
562
+ else
563
+ return find_network_group_by_name(val)
564
+ end
565
+ end
566
+
567
+ def find_network_group_by_id(id)
568
+ begin
569
+ json_response = @network_groups_interface.get(id.to_i)
570
+ return json_response['networkGroup']
571
+ rescue RestClient::Exception => e
572
+ if e.response && e.response.code == 404
573
+ print_red_alert "Network Group not found by id #{id}"
574
+ return nil
575
+ else
576
+ raise e
577
+ end
578
+ end
579
+ end
580
+
581
+ def find_network_group_by_name(name)
582
+ json_response = @network_groups_interface.list({name: name.to_s})
583
+ network_groups = json_response['networkGroups']
584
+ if network_groups.empty?
585
+ print_red_alert "Network Group not found by name #{name}"
586
+ return nil
587
+ elsif network_groups.size > 1
588
+ print_red_alert "#{network_groups.size} network groups found by name #{name}"
589
+ # print_networks_table(networks, {color: red})
590
+ rows = network_groups.collect do |network_group|
591
+ {id: it['id'], name: it['name']}
592
+ end
593
+ print red
594
+ tp rows, [:id, :name]
595
+ print reset,"\n"
596
+ return nil
597
+ else
598
+ return network_groups[0]
599
+ end
600
+ end
601
+
602
+ end