iface 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []