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.
@@ -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
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'iface/config'
4
+ require_relative 'iface/ip_address'
@@ -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: []