free-range 0.1.2 → 0.2.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.
- checksums.yaml +4 -4
- data/lib/free-range.rb +171 -31
- data/lib/free-range.rb.~ +237 -196
- data/lib/free-range.rb~ +531 -0
- metadata +16 -2
- data/bin/free-range.~ +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0252edadec09077b1c95f148f2e5dec18a0faf393f7c9b225fc0ca0962b06af
|
4
|
+
data.tar.gz: f45851705405c80dd7ec484c439711716146317e0b19efdba3e4d756f5fce2fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76e14e7c6e5a94c2f4a88fe987477e8ca960aab236fb2d63b24e7655a694d5e0da57a0b08aee141c4f6b4ecd4e1ec2caf64d671703033ab626a725de30c4a9a5
|
7
|
+
data.tar.gz: 879632d0abb8539b61b99e49af8bf427b786163be9e669e0a5542f2286234666d9c183a360fe21cac97bc9debb1ad97e816b15f18bd1cb494adfb205252c43ad
|
data/lib/free-range.rb
CHANGED
@@ -3,7 +3,6 @@ require 'rmagick'
|
|
3
3
|
require 'fileutils'
|
4
4
|
|
5
5
|
module FreeRange
|
6
|
-
|
7
6
|
# Перевірка наявності ImageMagick
|
8
7
|
begin
|
9
8
|
require 'rmagick'
|
@@ -13,6 +12,60 @@ module FreeRange
|
|
13
12
|
exit 1
|
14
13
|
end
|
15
14
|
|
15
|
+
# Клас для зберігання конфігураційних команд
|
16
|
+
class Config
|
17
|
+
attr_accessor :username, :password
|
18
|
+
|
19
|
+
# Ініціалізує об’єкт конфігурації
|
20
|
+
# @param login [Hash] Hash with target, username, and password
|
21
|
+
# @yield [self] Yields self for block-based configuration
|
22
|
+
def initialize(login)
|
23
|
+
@login = login
|
24
|
+
@username = nil
|
25
|
+
@password = nil
|
26
|
+
yield self if block_given?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Повертає команду SSH
|
30
|
+
# @return [String] SSH command string
|
31
|
+
def ssh_command
|
32
|
+
"sshpass -p \"#{@login[:password]}\" ssh -C -x -4 -o StrictHostKeyChecking=no #{@login[:username]}@#{@login[:target]}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Повертає команду для отримання даних про передплатників
|
36
|
+
# @return [String] Subscribers command string
|
37
|
+
def subscribers_command
|
38
|
+
"ssh -C -x roffice /usr/local/share/noc/bin/radius-subscribers"
|
39
|
+
end
|
40
|
+
|
41
|
+
# Повертає команду для отримання списку інтерфейсів
|
42
|
+
# @return [String] Command to fetch interfaces
|
43
|
+
def command_interfaces
|
44
|
+
'show configuration interfaces | no-more | display set | match dynamic-profile | match "ranges ([0-9]+(-[0-9]+)?)"'
|
45
|
+
end
|
46
|
+
|
47
|
+
# Повертає команду для отримання діапазонів VLAN
|
48
|
+
# @param interface [String, nil] Interface name or nil for all interfaces
|
49
|
+
# @return [String] Command to fetch VLAN ranges
|
50
|
+
def command_ranges(interface = nil)
|
51
|
+
interface ? "show configuration interfaces #{interface} | no-more | display set | match dynamic-profile | match \"ranges ([0-9]+(-[0-9]+)?)\"" : 'show configuration interfaces | no-more | display set | match dynamic-profile | match "ranges ([0-9]+(-[0-9]+)?)"'
|
52
|
+
end
|
53
|
+
|
54
|
+
# Повертає команду для отримання демультиплексорних VLAN
|
55
|
+
# @param interface [String, nil] Interface name or nil for all interfaces
|
56
|
+
# @return [String] Command to fetch demux VLANs
|
57
|
+
def command_demux(interface = nil)
|
58
|
+
interface ? "show configuration interfaces #{interface} | display set | match unnumbered-address" : 'show configuration interfaces | display set | match unnumbered-address'
|
59
|
+
end
|
60
|
+
|
61
|
+
# Повертає команду для отримання інших VLAN
|
62
|
+
# @param interface [String, nil] Interface name or nil for all interfaces
|
63
|
+
# @return [String] Command to fetch other VLANs
|
64
|
+
def command_another(interface = nil)
|
65
|
+
interface ? "show configuration interfaces #{interface} | display set | match vlan-id" : 'show configuration interfaces | display set | match vlan-id'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
16
69
|
# Абстрактний клас для роботи з VLAN
|
17
70
|
class VlanContainer
|
18
71
|
def initialize
|
@@ -20,16 +73,20 @@ module FreeRange
|
|
20
73
|
end
|
21
74
|
|
22
75
|
# Додаємо VLAN до списку (віртуальний метод, може бути перевизначений)
|
76
|
+
# @param vlan [Integer] VLAN ID to add
|
77
|
+
# @return [void]
|
23
78
|
def add_vlan(vlan)
|
24
79
|
@vlans << vlan
|
25
80
|
end
|
26
81
|
|
27
82
|
# Повертаємо масив VLAN
|
83
|
+
# @return [Array<Integer>] Sorted array of unique VLAN IDs
|
28
84
|
def vlans
|
29
85
|
@vlans.uniq.sort
|
30
86
|
end
|
31
87
|
|
32
88
|
# Створюємо хеш діапазонів із VLAN
|
89
|
+
# @return [Hash{Integer => Integer}] Hash mapping range start to range end
|
33
90
|
def ranges
|
34
91
|
return {} if @vlans.empty?
|
35
92
|
|
@@ -59,16 +116,23 @@ module FreeRange
|
|
59
116
|
end
|
60
117
|
|
61
118
|
# Додаємо діапазон до списку VLAN-ів
|
119
|
+
# @param start [Integer] Start of VLAN range
|
120
|
+
# @param finish [Integer] End of VLAN range
|
121
|
+
# @return [void]
|
62
122
|
def add_range(start, finish)
|
63
123
|
(start..finish).each { |vlan| @vlans << vlan }
|
64
124
|
end
|
65
125
|
|
66
126
|
# Додаємо діапазон до списку "інших" VLAN-ів
|
127
|
+
# @param start [Integer] Start of another VLAN range
|
128
|
+
# @param finish [Integer] End of another VLAN range
|
129
|
+
# @return [void]
|
67
130
|
def add_another_range(start, finish)
|
68
131
|
(start..finish).each { |vlan| @another_in_ranges << vlan }
|
69
132
|
end
|
70
133
|
|
71
134
|
# Повертаємо масив "інших" VLAN-ів
|
135
|
+
# @return [Array<Integer>] Sorted array of unique "another" VLAN IDs
|
72
136
|
def another_in_ranges
|
73
137
|
@another_in_ranges.uniq.sort
|
74
138
|
end
|
@@ -84,6 +148,9 @@ module FreeRange
|
|
84
148
|
|
85
149
|
# Клас для виводу даних
|
86
150
|
class Print
|
151
|
+
# Виводить хеш діапазонів VLAN у порядку зростання
|
152
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
153
|
+
# @return [void]
|
87
154
|
def self.ranged(ranges)
|
88
155
|
puts "\nСформований хеш діапазонів (в порядку зростання):"
|
89
156
|
if ranges.ranges.empty?
|
@@ -95,6 +162,9 @@ module FreeRange
|
|
95
162
|
end
|
96
163
|
end
|
97
164
|
|
165
|
+
# Виводить зайняті VLAN-и в межах діапазонів у порядку зростання
|
166
|
+
# @param vlans [Vlans] Object containing VLANs
|
167
|
+
# @return [void]
|
98
168
|
def self.vlans(vlans)
|
99
169
|
puts "\nЗайняті VLAN-и в межах діапазонів (в порядку зростання):"
|
100
170
|
if vlans.vlans.empty?
|
@@ -104,6 +174,9 @@ module FreeRange
|
|
104
174
|
end
|
105
175
|
end
|
106
176
|
|
177
|
+
# Виводить діапазони VLAN-ів у порядку зростання
|
178
|
+
# @param vlans [Vlans] Object containing VLANs
|
179
|
+
# @return [void]
|
107
180
|
def self.vlan_ranges(vlans)
|
108
181
|
puts "\nДіапазони VLAN-ів (в порядку зростання):"
|
109
182
|
vlan_ranges = vlans.vlan_ranges
|
@@ -116,6 +189,13 @@ module FreeRange
|
|
116
189
|
end
|
117
190
|
end
|
118
191
|
|
192
|
+
# Виводить комбіновані діапазони VLAN зі статусами
|
193
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
194
|
+
# @param vlans [Vlans] Object containing VLANs
|
195
|
+
# @param use_color [Boolean] Enable colored output
|
196
|
+
# @param target [String] Target device hostname
|
197
|
+
# @param interface [String, nil] Interface name or nil
|
198
|
+
# @return [void]
|
119
199
|
def self.combined_ranges(ranges, vlans, use_color, target, interface = nil)
|
120
200
|
if ranges.ranges.empty?
|
121
201
|
puts "Не знайдено діапазонів."
|
@@ -142,6 +222,13 @@ module FreeRange
|
|
142
222
|
puts result.join(',')
|
143
223
|
end
|
144
224
|
|
225
|
+
# Виводить таблицю розподілу VLAN
|
226
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
227
|
+
# @param vlans [Vlans] Object containing VLANs
|
228
|
+
# @param use_color [Boolean] Enable colored output
|
229
|
+
# @param target [String] Target device hostname
|
230
|
+
# @param interface [String, nil] Interface name or nil
|
231
|
+
# @return [void]
|
145
232
|
def self.table(ranges, vlans, use_color, target, interface = nil)
|
146
233
|
puts "VLAN Distribution for #{target}#{interface ? " (#{interface})" : ''}"
|
147
234
|
all_vlans, status_counts = build_vlan_statuses(ranges, vlans)
|
@@ -191,6 +278,13 @@ module FreeRange
|
|
191
278
|
puts summary_text
|
192
279
|
end
|
193
280
|
|
281
|
+
# Зберігає таблицю розподілу VLAN як PNG-зображення
|
282
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
283
|
+
# @param vlans [Vlans] Object containing VLANs
|
284
|
+
# @param path [String] Directory path to save the PNG
|
285
|
+
# @param target [String] Target device hostname
|
286
|
+
# @param interface [String, nil] Interface name or nil
|
287
|
+
# @return [void]
|
194
288
|
def self.table_png(ranges, vlans, path, target, interface = nil)
|
195
289
|
all_vlans, status_counts = build_vlan_statuses(ranges, vlans)
|
196
290
|
cell_width = 12
|
@@ -307,6 +401,10 @@ module FreeRange
|
|
307
401
|
|
308
402
|
private
|
309
403
|
|
404
|
+
# Будує хеш статусів VLAN і підраховує кількість кожного статусу
|
405
|
+
# @param ranges [Ranges] Object containing VLAN ranges
|
406
|
+
# @param vlans [Vlans] Object containing VLANs
|
407
|
+
# @return [Array<Hash, Hash>] Hash of VLAN statuses and status counts
|
310
408
|
def self.build_vlan_statuses(ranges, vlans)
|
311
409
|
all_vlans = {}
|
312
410
|
ranges.ranges.each do |start, finish|
|
@@ -321,6 +419,12 @@ module FreeRange
|
|
321
419
|
[all_vlans, status_counts]
|
322
420
|
end
|
323
421
|
|
422
|
+
# Форматує діапазон VLAN зі статусом для виводу
|
423
|
+
# @param start [Integer] Start of VLAN range
|
424
|
+
# @param finish [Integer] End of VLAN range
|
425
|
+
# @param status [String] VLAN status ('f', 'b', 'e', 'c', 'a', 'u')
|
426
|
+
# @param use_color [Boolean] Enable colored output
|
427
|
+
# @return [String] Formatted range string
|
324
428
|
def self.format_range(start, finish, status, use_color)
|
325
429
|
range_text = start == finish ? "#{start}" : "#{start}-#{finish}"
|
326
430
|
range_text_with_status = "#{range_text}(#{status})"
|
@@ -339,6 +443,10 @@ module FreeRange
|
|
339
443
|
end
|
340
444
|
end
|
341
445
|
|
446
|
+
# Форматує символ для таблиці VLAN
|
447
|
+
# @param status [String] VLAN status ('f', 'b', 'e', 'c', 'a', 'u', or ' ')
|
448
|
+
# @param use_color [Boolean] Enable colored output
|
449
|
+
# @return [String] Formatted character for table display
|
342
450
|
def self.format_table_char(status, use_color)
|
343
451
|
if use_color
|
344
452
|
case status
|
@@ -357,12 +465,14 @@ module FreeRange
|
|
357
465
|
end
|
358
466
|
|
359
467
|
# Метод для заповнення Ranges для одного інтерфейсу
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
468
|
+
# @param config [Config] Configuration object with commands
|
469
|
+
# @param interface [String, nil] Interface name or nil for all interfaces
|
470
|
+
# @param ranges [Ranges] Object to store VLAN ranges
|
471
|
+
# @param debug [Boolean] Enable debug output
|
472
|
+
# @return [void]
|
473
|
+
def self.process_interface(config, interface, ranges, debug = false)
|
474
|
+
full_cmd = "#{config.ssh_command} '#{config.command_ranges(interface)}'"
|
475
|
+
puts "[DEBUG] Executing command: #{full_cmd}" if debug
|
366
476
|
result = `#{full_cmd}`.strip
|
367
477
|
unless result.empty?
|
368
478
|
result.each_line do |line|
|
@@ -374,7 +484,8 @@ module FreeRange
|
|
374
484
|
end
|
375
485
|
end
|
376
486
|
|
377
|
-
full_cmd = "#{ssh_command} '#{command_demux}'"
|
487
|
+
full_cmd = "#{config.ssh_command} '#{config.command_demux(interface)}'"
|
488
|
+
puts "[DEBUG] Executing command: #{full_cmd}" if debug
|
378
489
|
result = `#{full_cmd}`.strip
|
379
490
|
unless result.empty?
|
380
491
|
result.each_line do |line|
|
@@ -388,7 +499,8 @@ module FreeRange
|
|
388
499
|
end
|
389
500
|
end
|
390
501
|
|
391
|
-
full_cmd = "#{ssh_command} '#{command_another}'"
|
502
|
+
full_cmd = "#{config.ssh_command} '#{config.command_another(interface)}'"
|
503
|
+
puts "[DEBUG] Executing command: #{full_cmd}" if debug
|
392
504
|
result = `#{full_cmd}`.strip
|
393
505
|
unless result.empty?
|
394
506
|
result.each_line do |line|
|
@@ -404,10 +516,16 @@ module FreeRange
|
|
404
516
|
end
|
405
517
|
|
406
518
|
# Основна логіка виконання
|
519
|
+
# @return [void]
|
407
520
|
def self.run
|
408
521
|
options = {}
|
409
522
|
OptionParser.new do |opts|
|
410
|
-
opts.banner =
|
523
|
+
opts.banner = <<~BANNER
|
524
|
+
Використання: free-range <IP-адреса або hostname> [опції]
|
525
|
+
|
526
|
+
Аналізує розподіл VLAN на мережевих пристроях, генеруючи таблиці або PNG-зображення.
|
527
|
+
BANNER
|
528
|
+
opts.on("-h", "--help", "Показати цю довідку") { puts opts; exit 0 }
|
411
529
|
opts.on("-u", "--username USERNAME", "Ім'я користувача для SSH") { |u| options[:username] = u }
|
412
530
|
opts.on("-p", "--password PASSWORD", "Пароль для SSH") { |p| options[:password] = p }
|
413
531
|
opts.on("-n", "--no-color", "Вимкнути кольоровий вивід") { options[:no_color] = true }
|
@@ -415,50 +533,72 @@ module FreeRange
|
|
415
533
|
opts.on("-t", "--table", "Вивести діаграму розподілу VLAN-ів") { options[:table] = true }
|
416
534
|
opts.on("-g", "--table-png PATH", "Зберегти діаграму розподілу VLAN-ів як PNG") { |path| options[:table_png] = path }
|
417
535
|
opts.on("-i", "--interface INTERFACE", "Назва інтерфейсу або 'all'") { |i| options[:interface] = i }
|
536
|
+
opts.on("-c", "--config CONFIG_FILE", "Шлях до конфігураційного файлу") { |c| options[:config_file] = c }
|
418
537
|
end.parse!
|
419
538
|
|
420
539
|
if ARGV.empty?
|
421
540
|
puts "Помилка: потрібно вказати IP-адресу або hostname роутера."
|
422
|
-
puts "
|
423
|
-
exit 1
|
424
|
-
end
|
425
|
-
|
426
|
-
username = options[:username] || ENV['WHOAMI']
|
427
|
-
password = options[:password] || ENV['WHATISMYPASSWD']
|
428
|
-
if username.nil? || password.nil?
|
429
|
-
puts "Помилка: необхідно вказати ім'я користувача та пароль."
|
430
|
-
puts "Використовуйте опції -u|--username і -p|--password або змінні оточення WHOAMI і WHATISMYPASSWD."
|
541
|
+
puts "Використовуйте: free-range --help для довідки."
|
431
542
|
exit 1
|
432
543
|
end
|
433
544
|
|
545
|
+
# Визначаємо змінні з опцій
|
434
546
|
use_color = !options[:no_color] && ENV['TERM'] && ENV['TERM'] != 'dumb'
|
435
547
|
debug = options[:debug]
|
436
548
|
table_mode = options[:table]
|
437
549
|
table_png_mode = options[:table_png]
|
438
550
|
interface = options[:interface]
|
551
|
+
config_file = options[:config_file]
|
552
|
+
|
553
|
+
# Ініціалізуємо config з порожнім login
|
554
|
+
config = Config.new({ target: ARGV[0], username: nil, password: nil })
|
555
|
+
|
556
|
+
# Завантажуємо конфігураційний файл, якщо він вказаний
|
557
|
+
if config_file
|
558
|
+
begin
|
559
|
+
# Виконуємо конфігураційний файл у контексті існуючого об’єкта config
|
560
|
+
config.instance_eval(File.read(config_file), config_file)
|
561
|
+
rescue LoadError, Errno::ENOENT
|
562
|
+
puts "Помилка: неможливо завантажити конфігураційний файл '#{config_file}'."
|
563
|
+
exit 1
|
564
|
+
rescue ArgumentError => e
|
565
|
+
puts "Помилка в конфігураційному файлі '#{config_file}': #{e.message}"
|
566
|
+
exit 1
|
567
|
+
rescue StandardError => e
|
568
|
+
puts "Помилка в конфігураційному файлі '#{config_file}': #{e.message}"
|
569
|
+
exit 1
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
# Визначаємо username і password з пріоритетом: аргументи > config > ENV
|
574
|
+
username = options[:username] || config.username || ENV['WHOAMI']
|
575
|
+
password = options[:password] || config.password || ENV['WHATISMYPASSWD']
|
439
576
|
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
577
|
+
if username.nil? || password.nil?
|
578
|
+
puts "Помилка: необхідно вказати ім'я користувача та пароль."
|
579
|
+
puts "Використовуйте опції -u/--username і -p/--password, конфігураційний файл або змінні оточення WHOAMI і WHATISMYPASSWD."
|
580
|
+
exit 1
|
581
|
+
end
|
445
582
|
|
446
583
|
login = { target: ARGV[0], username: username, password: password }
|
447
584
|
target = ARGV[0].split('.')[0]
|
448
585
|
puts "Connecting to device: #{login[:target]}"
|
449
586
|
|
450
|
-
|
451
|
-
|
587
|
+
# Оновлюємо config з актуальними login даними
|
588
|
+
config = Config.new(login) { |c|
|
589
|
+
c.username = config.username if config.username
|
590
|
+
c.password = config.password if config.password
|
591
|
+
}
|
452
592
|
|
453
|
-
subscribers_result = `#{subscribers_command}`.strip
|
593
|
+
subscribers_result = `#{config.subscribers_command}`.strip
|
454
594
|
if subscribers_result.empty?
|
455
595
|
puts "Помилка: результат subscribers_command порожній. Перевір шлях або доступ."
|
456
596
|
exit 1
|
457
597
|
end
|
458
598
|
|
459
599
|
if interface == "all"
|
460
|
-
|
461
|
-
|
600
|
+
full_cmd = "#{config.ssh_command} '#{config.command_interfaces}'"
|
601
|
+
puts "[DEBUG] Executing command: #{full_cmd}" if debug
|
462
602
|
result = `#{full_cmd}`.strip
|
463
603
|
if result.empty?
|
464
604
|
puts "Помилка: результат команди порожній. Перевір підключення або команду."
|
@@ -481,7 +621,7 @@ module FreeRange
|
|
481
621
|
end
|
482
622
|
end
|
483
623
|
|
484
|
-
process_interface(
|
624
|
+
process_interface(config, intf, ranges, debug)
|
485
625
|
if debug
|
486
626
|
puts "\nІнтерфейс: #{intf}"
|
487
627
|
Print.ranged(ranges)
|
@@ -511,7 +651,7 @@ module FreeRange
|
|
511
651
|
end
|
512
652
|
end
|
513
653
|
|
514
|
-
process_interface(
|
654
|
+
process_interface(config, interface, ranges, debug)
|
515
655
|
if debug
|
516
656
|
puts "\nІнтерфейс: #{interface}" if interface
|
517
657
|
Print.ranged(ranges)
|