iface 0.1.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 +7 -0
- data/lib/iface.rb +4 -0
- data/lib/iface/config.rb +58 -0
- data/lib/iface/config_file.rb +167 -0
- data/lib/iface/ip_address.rb +13 -0
- data/lib/iface/ip_helpers.rb +33 -0
- data/lib/iface/value_set.rb +45 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0dabac73d94d38d1e887f22ad10f75ae1c40a212
|
4
|
+
data.tar.gz: 5b36a303566537a64be7b4d11bcb2030a7894baf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 72c5525357faea04f9244a122dcd92cea7de287860481db15af8450f46fa4b4febacddceaee364a4ffe995e38d5762ce6d9743f44393f35f70698d0359ec512a
|
7
|
+
data.tar.gz: 05d5d2fabe7b9b831d990bf5039a5ee1aef7fd6f6c5635011f869bdef084c2f863bdc0661f8eebb6d3cb0c8676d2786c0d389502a15460448690da58cd955d69
|
data/lib/iface.rb
ADDED
data/lib/iface/config.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'config_file'
|
4
|
+
require 'ipaddr'
|
5
|
+
|
6
|
+
module Iface
|
7
|
+
# Represents a set of ConfigFiles for network interface configuration
|
8
|
+
class Config
|
9
|
+
Reserved_IP_Ranges = %w{10.0.0.0/8 192.168.0.0/16}.collect { |i| IPAddr.new(i) }
|
10
|
+
|
11
|
+
def self.discover(pattern)
|
12
|
+
new.tap do |config|
|
13
|
+
Dir.glob(pattern) do |fullname|
|
14
|
+
puts fullname
|
15
|
+
File.open(fullname) { |io| config.add(fullname, io) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@files = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def add(filename, io)
|
25
|
+
file = ConfigFile.create(filename, io)
|
26
|
+
file_type = file.class.file_type_name
|
27
|
+
if @files.key?(file_type)
|
28
|
+
@files[file_type] << file
|
29
|
+
else
|
30
|
+
@files[file_type] = [file]
|
31
|
+
end
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the PrimaryFile
|
36
|
+
#
|
37
|
+
# There should be 0 or 1 of these; else it's an error.
|
38
|
+
def primary
|
39
|
+
result = @files[:primary].find_all do |file|
|
40
|
+
if file.ip_address.nil?
|
41
|
+
false
|
42
|
+
else
|
43
|
+
ipaddr = IPAddr.new(file.ip_address)
|
44
|
+
!Reserved_IP_Ranges.any? { |range| range.include?(ipaddr) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
case result.size
|
49
|
+
when 0
|
50
|
+
nil
|
51
|
+
when 1
|
52
|
+
result.first
|
53
|
+
else
|
54
|
+
raise RuntimeError, "Expected 0 or 1 primary files; found #{result.size}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'ip_helpers'
|
4
|
+
require_relative 'value_set'
|
5
|
+
|
6
|
+
class String
|
7
|
+
def decamelize
|
8
|
+
gsub(/[A-Z]/) {|m| $` == '' ? m.downcase : "_#{m.downcase}"}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Iface
|
13
|
+
# Base class for a network interface config file
|
14
|
+
class ConfigFile
|
15
|
+
def self.create(filename, io)
|
16
|
+
fname = File.split(filename).last
|
17
|
+
device, range_num, clone_num = parse_filename(fname)
|
18
|
+
vars = ValueSet.new(io)
|
19
|
+
|
20
|
+
FILE_TYPES.each do |klass|
|
21
|
+
if klass.recognize?(device, range_num, clone_num, vars)
|
22
|
+
return klass.new(filename, device, range_num, clone_num, vars)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
raise ArgumentError, "Input not recognized from file #{fname}: #{[device, range_num, clone_num, vars].inspect}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.parse_filename(filename)
|
30
|
+
match = filename.match(/\Aifcfg-(\w+)((-range(\d+))|(:(\d+)))?\Z/)
|
31
|
+
if match
|
32
|
+
device, _skip0, _skip1, range_num, _skip2, clone_num = match.captures
|
33
|
+
[device, range_num&.to_i, clone_num&.to_i]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.recognize?(_device, _range_num, _clone_num, _vars)
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.file_type_name
|
42
|
+
if self.name =~ /File\Z/
|
43
|
+
self.name.split('::').last[0..-5].decamelize.to_sym
|
44
|
+
else
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(filename, device, _range_num, _clone_num, _vars)
|
50
|
+
@filename = filename
|
51
|
+
@device = device
|
52
|
+
end
|
53
|
+
|
54
|
+
def static?
|
55
|
+
raise NotImplementedError
|
56
|
+
end
|
57
|
+
|
58
|
+
def include?(_ip)
|
59
|
+
raise NotImplementedError
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Represents a primary config file (not loopback, range or clone file)
|
64
|
+
#
|
65
|
+
# These are files named like "ifcfg-eth0".
|
66
|
+
class PrimaryFile < ConfigFile
|
67
|
+
attr_reader :ip_address, :ipv6_address, :ipv6_secondaries
|
68
|
+
|
69
|
+
def self.recognize?(device, range_num, clone_num, _vars)
|
70
|
+
device != 'lo' && range_num.nil? && clone_num.nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(filename, device, range_num, clone_num, vars)
|
74
|
+
super
|
75
|
+
if (vars['bootproto'] == 'static') || (vars['bootproto'] == 'none') # RHEL6 uses "none"
|
76
|
+
@ip_address = vars['ipaddr']
|
77
|
+
@ipv6_address = vars['ipv6addr']
|
78
|
+
@ipv6_secondaries = vars['ipv6addr_secondaries']&.split(/\s+/)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def static?
|
83
|
+
!@ip_address.nil?
|
84
|
+
end
|
85
|
+
|
86
|
+
def include?(ip)
|
87
|
+
@ip_address == ip
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Represents a clone config file (single IP address)
|
92
|
+
#
|
93
|
+
# These are files named like "ifcfg-eth0:1".
|
94
|
+
class CloneFile < ConfigFile
|
95
|
+
attr_reader :ip_address, :clone_num
|
96
|
+
|
97
|
+
def self.recognize?(_device, _range_num, clone_num, _vars)
|
98
|
+
!clone_num.nil?
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize(filename, device, _range_num, clone_num, vars)
|
102
|
+
super
|
103
|
+
@ip_address = vars['ipaddr']
|
104
|
+
@clone_num = clone_num
|
105
|
+
end
|
106
|
+
|
107
|
+
def static?
|
108
|
+
true
|
109
|
+
end
|
110
|
+
|
111
|
+
def include?(ip)
|
112
|
+
@ip_address == ip
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Represents a range config file (a range of IP addresses)
|
117
|
+
#
|
118
|
+
# These are files named like "ifcfg-eth0-range0".
|
119
|
+
class RangeFile < ConfigFile
|
120
|
+
attr_reader :start_clone_num
|
121
|
+
|
122
|
+
def self.recognize?(_device, range_num, _clone_num, _vars)
|
123
|
+
!range_num.nil?
|
124
|
+
end
|
125
|
+
|
126
|
+
def initialize(filename, device, range_num, clone_num, vars)
|
127
|
+
super
|
128
|
+
@start_ip_num = string_to_ip_num(vars['ipaddr_start'])
|
129
|
+
@end_ip_num = string_to_ip_num(vars['ipaddr_end'])
|
130
|
+
@start_clone_num = vars['clonenum_start']&.to_i
|
131
|
+
end
|
132
|
+
|
133
|
+
def static?
|
134
|
+
true
|
135
|
+
end
|
136
|
+
|
137
|
+
def include?(ip)
|
138
|
+
ip_num = string_to_ip_num(ip)
|
139
|
+
@start_ip_num <= ip_num && ip_num <= @end_ip_num
|
140
|
+
end
|
141
|
+
|
142
|
+
def start_ip_num
|
143
|
+
@start_ip_num.to_ip
|
144
|
+
end
|
145
|
+
|
146
|
+
def end_ip_num
|
147
|
+
@end_ip_num.to_ip
|
148
|
+
end
|
149
|
+
|
150
|
+
def string_to_ip_num(str)
|
151
|
+
str.split('.').collect { |x| x.to_i.to_s(16).rjust(2, '0') }.join.hex
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class LoopbackFile < ConfigFile
|
156
|
+
def self.recognize?(device, _range_num, _clone_num, _vars)
|
157
|
+
device == 'lo'
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
FILE_TYPES = [
|
162
|
+
PrimaryFile,
|
163
|
+
CloneFile,
|
164
|
+
RangeFile,
|
165
|
+
LoopbackFile
|
166
|
+
].freeze
|
167
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ipaddr'
|
4
|
+
|
5
|
+
module Iface
|
6
|
+
class IpAddress
|
7
|
+
def initialize(*args)
|
8
|
+
@ipaddr = IPAddr.new(*args)
|
9
|
+
@_bitmask = @ipaddr.instance_eval { @mask_addr.to_s(2) }
|
10
|
+
@inverse_mask_addr = @_bitmask.tr('01', '10').to_i(2)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Adds IP-related methods to all Integers
|
4
|
+
class Integer
|
5
|
+
Mask32_ = 0xffffffffffffffff
|
6
|
+
|
7
|
+
def to_ip(ipver = 4)
|
8
|
+
to_ipaddr(ipver).to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_ipaddr4
|
12
|
+
IPAddr.new_ntoh([self].pack('N'))
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_ipaddr6
|
16
|
+
IPAddr.new_ntoh([(self >> 96), (self >> 64) & Mask32_, (self >> 32) & Mask32_, self & Mask32_].pack('N*'))
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_ipaddr(ipver = 4)
|
20
|
+
case ipver
|
21
|
+
when 4
|
22
|
+
to_ipaddr4
|
23
|
+
when 6
|
24
|
+
to_ipaddr6
|
25
|
+
else
|
26
|
+
raise ArgumentError, "Expecting argument 1 to be either 4 or 6; got #{ipver.inspect}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def max_mask_bits
|
31
|
+
to_s(2)[/(0*)$/, 1].size
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Iface
|
4
|
+
# Represents a set of NAME=value pairs
|
5
|
+
class ValueSet
|
6
|
+
# Represents a NAME=value pair
|
7
|
+
class Pair
|
8
|
+
attr_reader :name, :value
|
9
|
+
|
10
|
+
def initialize(line)
|
11
|
+
match = line.match(/(^[A-Z0-9_]+)="?(.*?)"?$/)
|
12
|
+
raise ArgumentError, "Expected pattern NAME=value; got #{line.inspect}" unless match
|
13
|
+
@name = match[1]
|
14
|
+
@value = match[2]&.sub(/^"/, '')&.sub(/"$/, '')
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"#{@name}=\"#{@value}\""
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(io)
|
23
|
+
@vars = {}
|
24
|
+
io.each_line do |line|
|
25
|
+
edited_line = line.sub(/#.*$/, '').strip
|
26
|
+
next if edited_line.empty?
|
27
|
+
pair = Pair.new(edited_line)
|
28
|
+
@vars[pair.name] = pair
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](name)
|
33
|
+
@vars[name.upcase]&.value
|
34
|
+
end
|
35
|
+
|
36
|
+
def key?(name)
|
37
|
+
@vars.key?(name.upcase)
|
38
|
+
end
|
39
|
+
alias has_key? key?
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
@vars.values.map(&:to_s).join("\n")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: iface
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jim Cain
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-02 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email: camelotjim@jcain.net
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/iface.rb
|
20
|
+
- lib/iface/config.rb
|
21
|
+
- lib/iface/config_file.rb
|
22
|
+
- lib/iface/ip_address.rb
|
23
|
+
- lib/iface/ip_helpers.rb
|
24
|
+
- lib/iface/value_set.rb
|
25
|
+
homepage: http://rubygems.org/gems/hola
|
26
|
+
licenses:
|
27
|
+
- BSD-3-Clause
|
28
|
+
metadata: {}
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubyforge_project:
|
45
|
+
rubygems_version: 2.6.11
|
46
|
+
signing_key:
|
47
|
+
specification_version: 4
|
48
|
+
summary: Configures network interfaces on Red Hat systems
|
49
|
+
test_files: []
|