iqeo-hostspec 0.1.0.pre1 → 0.1.0.pre2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 08702c71ab8703e8556dff864bcfc5af505a898f
4
- data.tar.gz: e5934ce4e4a66002865c86c829fdcdbb5019e153
3
+ metadata.gz: 4b3e2f1c15026a95d95666c9eaee3de460ccf92d
4
+ data.tar.gz: 263459817766d46b9b32d4bfee19f546134817b0
5
5
  SHA512:
6
- metadata.gz: c6ef52db6597eacbc79792df006d91b0b905c1296f8778058f8ade392ba53f995743b9dc53175393b8b4b98100aff09a600cd37c14b4c1a56a9e49fde2b59a69
7
- data.tar.gz: 33a876331bfc2bafefc004709c7dd45327a902aceea9dacff839fa691b446e77b9f9c3d971c4dee2ca59c6cbeb34f0cf058d525796bae27a2d0f9859c827b5e3
6
+ metadata.gz: 0be30e4d2da2a60e74561d4a224e8cf85c4f3d5477ede05859aa6a62f58983122d907489920071acd200bd19dfd53bef9ecd3594b576354ac102b61b70436404
7
+ data.tar.gz: a07aabde5df2381c9d2f4b379a16e8c758c422c773e16d665b3d2af1ff84ef2b85876cd0c54fb55a72d2e87512782d5212847735fd216f4b2bac1c64acc66904
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - "2.1.0"
4
+ - "2.0.0"
5
+ script: bundle exec rspec
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Iqeo::Hostspec
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/iqeo-hostspec.png)](http://badge.fury.io/rb/iqeo-hostspec)
4
+ [![Build Status](https://travis-ci.org/iqeo/iqeo-hostspec.png?branch=master)](https://travis-ci.org/iqeo/iqeo-hostspec)
5
+
3
6
  TODO: Write a gem description
4
7
 
5
8
  ## Installation
@@ -20,10 +23,3 @@ Or install it yourself as:
20
23
 
21
24
  TODO: Write usage instructions here
22
25
 
23
- ## Contributing
24
-
25
- 1. Fork it
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
data/bin/hostspec CHANGED
@@ -2,130 +2,5 @@
2
2
 
3
3
  require 'iqeo/hostspec'
4
4
 
5
- class Runner
6
-
7
- def self.run args, out: $stdout, err: $stderr
8
-
9
- if args.empty?
10
- err.puts "Error: No specs given"
11
- return 1
12
- end
13
-
14
- if args.include?('-h') || args.include?('--help')
15
- print_help err
16
- return 0
17
- end
18
-
19
- if args.include?('-v') || args.include?('--version')
20
- print_version err
21
- return 0
22
- end
23
-
24
- cmd_sw_index = args.index('-c') || args.index('--cmd')
25
- if cmd_sw_index
26
- specs = args.take(cmd_sw_index) # specs are before switch
27
- cmd = args.drop(cmd_sw_index+1).join ' ' # command components are after switch, join into string to pass to subshell
28
- if cmd.empty?
29
- err.puts "Error: No command given"
30
- return 2
31
- end
32
- else
33
- specs = args
34
- cmd = nil
35
- end
36
-
37
- results = []
38
- specs.each do |spec|
39
- spec = spec.strip
40
- begin
41
- host_spec = Iqeo::Hostspec.new spec
42
- rescue Exception => e
43
- err.puts "Error: #{e.message}"
44
- return 3
45
- end
46
- if cmd.nil?
47
- host_spec.each { |address| out.puts address }
48
- else
49
- env = {
50
- 'HOSTSPEC_MASK' => host_spec.mask,
51
- 'HOSTSPEC_MASKLEN' => host_spec.mask_length.to_s,
52
- 'HOSTSPEC_COUNT' => host_spec.size.to_s
53
- }
54
- host_spec.each_with_index do |address,index|
55
- env['HOSTSPEC_IP'] = address
56
- env['HOSTSPEC_INDEX'] = (index+1).to_s
57
- # this uses posix 'sh', to use bash user's cmd should be 'bash -c "echo \$HOSTSPEC_IP"' and deal with the weird nested quoting
58
- results << system( env, cmd )
59
- end
60
- end
61
- end
62
- ( results.empty? || results.all? ) ? 0 : 4
63
- end
64
-
65
- def self.print_help io
66
- io.puts "Usage: hostspec [ options ] specs... [ [ -c / --cmd ] command ]"
67
- io.puts
68
- io.puts "Prints all IP addresses for IP host specifications (see Specs:)."
69
- io.puts "If specified, a command is executed for each IP address, with related values in environment variables (see Command:)."
70
- io.puts
71
- io.puts "Specs:"
72
- io.puts " Nmap-style IP host specifications, multiple specs separated by spaces."
73
- io.puts " Single host : x.x.x.x or hostname"
74
- io.puts " Multiple hosts:"
75
- io.puts " - by mask length : x.x.x.x/m or hostname/m"
76
- io.puts " - by octet values : x.x.x.a,b,c"
77
- io.puts " - by octet ranges : x.x.x.d-e"
78
- io.puts " Octet values and ranges may be combined or applied to any/multiple octets."
79
- io.puts " Examples:"
80
- io.puts " hostname : localhost => 127.0.0.1"
81
- io.puts " hostname w/mask : localhost/24 => 127.0.0.0 127.0.0.1 ... 127.0.0.254 127.0.0.255"
82
- io.puts " address : 1.1.1.1 => 1.1.1.1"
83
- io.puts " address w/mask : 2.2.2.1/24 => 2.2.2.0 2.2.2.1 ... 2.2.2.254 2.2.2.255"
84
- io.puts " address w/values : 3.3.3.10,20,30 => 3.3.3.10 3.3.3.20 3.3.3.30"
85
- io.puts " address w/ranges : 4.4.4.40-50 => 4.4.4.40 4.4.4.41 ... 4.4.4.49 4.4.4.50"
86
- io.puts " address w/combo : 5.5.5.2,4-6,8 => 5.5.5.2 5.5.5.4 5.5.5.5 5.5.5.6 5.5.5.8"
87
- io.puts " complex : 6.1-2,3.4-5.6 => 6.1.4.6 6.1.5.6 6.2.4.6 6.2.5.6 6.3.4.6 6.3.5.6"
88
- io.puts
89
- io.puts "Command:"
90
- io.puts " A command to execute for each IP address may be specified following the command switch ( -c / --cmd )."
91
- io.puts " The command is executed in a separate shell for each IP address."
92
- io.puts " Environment variables are provided with values for each IP address command execution."
93
- io.puts " Quote these variables in the command to prevent substitution by the current shell."
94
- io.puts " $HOSTSPEC_IP : IP address"
95
- io.puts " $HOSTSPEC_MASK : Mask (255.255.255.255 if a mask length was not specified)"
96
- io.puts " $HOSTSPEC_MASKLEN : Mask length (32 if a mask length was not specified)"
97
- io.puts " $HOSTSPEC_COUNT : Count of IP addresses"
98
- io.puts " $HOSTSPEC_INDEX : Index of IP address (from 1 to Count)"
99
- io.puts " Examples:"
100
- io.puts " Print IP addresses and mask length with index and count:"
101
- io.puts " hostspec 1.1.1.1-3 --cmd echo '$HOSTSPEC_INDEX' of '$HOSTSPECT_COUNT' : '$HOSTSPEC_IP/$HOSTSPEC_MASKLEN'"
102
- io.puts " ..."
103
- io.puts " 1 of 3 : 1.1.1.1/24"
104
- io.puts " 2 of 3 : 1.1.1.2/24"
105
- io.puts " 3 of 3 : 1.1.1.3/24"
106
- io.puts " Collect routing tables of all hosts on a network via ssh:"
107
- io.puts " hostspec 1.1.1.1-254 --cmd 'ssh me@$HOSTSPEC_IP route -n'"
108
- io.puts " Collect default web pages from all servers on a network via curl:"
109
- io.puts " hostspec 1.1.1.1-254 --cmd curl -o '$HOSTSPEC_IP.html' 'http://$HOSTSPEC_IP'"
110
- io.puts " Collect IP configuration info from multiple windows systems (run from a windows system):"
111
- io.puts " hostspec 1.1.1.1-254 --cmd psexec '\\%HOSTSPEC_IP%' ipconfig /all"
112
- io.puts " Collect IP configuration info from multiple windows systems (run from a linux system with kerberos):"
113
- io.puts " hostspec 1.1.1.1-254 --cmd winexe --kerberos yes //$(dig -x '$HOSTSPEC_IP' +short) ipconfig /all"
114
- io.puts " ...or any task that you would have to execute individually on multiple systems."
115
- io.puts
116
- io.puts "Options:"
117
- io.puts " -h / --help : Display this helpful information"
118
- io.puts " -v / --version : Display program version"
119
- io.puts
120
- end
121
-
122
- def self.print_version io
123
- io.puts "hostspec version #{Iqeo::Hostspec::VERSION}"
124
- end
125
-
126
- end
127
-
128
- if __FILE__ == $0
129
- exit Runner.run ARGV
130
- end
5
+ exit Iqeo::Hostspec::Runner.run ARGV
131
6
 
@@ -0,0 +1,179 @@
1
+ require 'resolv'
2
+
3
+ module Iqeo
4
+ module Hostspec
5
+
6
+ class HostspecException < Exception ; end
7
+
8
+ class Hostspec
9
+
10
+ include Enumerable
11
+
12
+ attr_reader :string, :mask, :mask_length, :address_spec, :hostname
13
+
14
+ def initialize spec_str
15
+ @string = spec_str
16
+ raise HostspecException, 'spec cannot be empty' if spec_str.empty?
17
+ host_str, mask_str = split_on_slash spec_str
18
+ raise HostspecException, 'host cannot be empty' if host_str.empty?
19
+ parse_mask mask_str
20
+ begin
21
+ parse_address_spec host_str
22
+ rescue HostspecException
23
+ parse_hostname host_str
24
+ end
25
+ raise HostspecException, 'complex spec cannot have mask length' if @mask_specified && @address_spec.any? { |octet| octet.size > 1 }
26
+ mask_address_spec
27
+ end
28
+
29
+ def split_on_slash str
30
+ case str.count '/'
31
+ when 0 then [ str.strip, '' ]
32
+ when 1 then str.strip.split '/'
33
+ else raise 'bad format, expected 0 or 1 "/"'
34
+ end
35
+ end
36
+
37
+ def parse_mask str
38
+ if str.empty?
39
+ @mask = '255.255.255.255'
40
+ @mask_length = 32
41
+ @mask_specified = false
42
+ return
43
+ end
44
+ if match = str.match( /^\d+$/ )
45
+ @mask_length = match[0].to_i
46
+ raise "bad mask length (#{@mask_length}), expected between 0 ad 32" unless @mask_length.between? 0,32
47
+ mask_int = ((2**@mask_length)-1) << (32-@mask_length)
48
+ @mask = [24,16,8,0].collect { |n| ( mask_int & ( 255 << n ) ) >> n }.join '.'
49
+ @mask_specified = true
50
+ else
51
+ raise "bad format, expected mask length after '/'"
52
+ end
53
+ end
54
+
55
+ def mask_address_spec
56
+ @address_spec.each_with_index do |octet,index|
57
+ high_bit_position = ( index * 8 ) + 1
58
+ low_bit_position = ( index + 1 ) * 8
59
+ @address_spec[index] = case
60
+ when @mask_length >= low_bit_position then octet
61
+ when @mask_length < high_bit_position then [0..255]
62
+ else
63
+ octet_mask_length = @mask_length % 8
64
+ octet_mask = ( ( 2 ** octet_mask_length ) - 1 ) << ( 8 - octet_mask_length )
65
+ octet_mask_inverted = octet_mask ^ 255
66
+ octet_min = octet_mask & octet[0]
67
+ octet_max = octet_min | octet_mask_inverted
68
+ [octet_min..octet_max]
69
+ end
70
+ end
71
+ end
72
+
73
+ def parse_address_spec str
74
+ octet_strs = str.split '.'
75
+ raise HostspecException, 'bad ip, expected 4 octets' unless octet_strs.size == 4
76
+ octets = octet_strs.collect { |octet_str| parse_octet octet_str }
77
+ @address_spec = octets
78
+ end
79
+
80
+ def parse_octet str
81
+ values = str.split ','
82
+ values.collect { |value_str| parse_octet_value value_str }
83
+ end
84
+
85
+ def parse_octet_value str
86
+ # values may be dash denoted ranges, possibilities...
87
+ # n : just a number : 'n'.split '-' == ['n'] <= same = problem! 'n'.split '-', -1 == [ "n" ]
88
+ # n-m : range from n to m : 'n-m'.split '-' == ['n','m'] 'n-m'.split '-', -1 == [ "n" , "m" ]
89
+ # n- : range from n to 255 : 'n-'.split '-' == ['n'] <= same = problem! 'n-'.split '-', -1 == [ "n" , "" ]
90
+ # -m : range from 0 to m : '-m'.split '-' == ['','m'] '-m'.split '-', -1 == [ "" , "m" ]
91
+ # - : range from 0 to 255 : '-'.split '-' == [] '-'.split '-', -1 == [ "" , "" ]
92
+ numbers = str.split '-', -1 # maximize return fields to distinguish 'n' from '-m'
93
+ case numbers.size
94
+ when 1 then
95
+ check_octet_value numbers[0]
96
+ numbers[0].to_i
97
+ when 2 then
98
+ numbers[0] = '0' if numbers[0].empty?
99
+ numbers[1] = '255' if numbers[1].empty?
100
+ check_octet_value numbers[0]
101
+ check_octet_value numbers[1]
102
+ range_start = numbers[0].to_i
103
+ range_finish = numbers[1].to_i
104
+ raise HostspecException, "bad ip, reversed range in octet value: #{str}" if range_start > range_finish
105
+ range_start..range_finish
106
+ else
107
+ raise HostspecException, "bad ip, invalid octet value: #{str}"
108
+ end
109
+ end
110
+
111
+ def check_octet_value str
112
+ match = str.match /^(25[0-5]|2[0-4]\d|[0-1]\d\d|\d\d|\d)$/
113
+ raise HostspecException, "bad ip, octet value is not a number in 0-255: #{str}" unless match
114
+ end
115
+
116
+ def parse_hostname str
117
+ @hostname = str
118
+ parse_address_spec Resolv.getaddress(str)
119
+ end
120
+
121
+ def recursively_iterate_octets octet_index = 0, address = [], &block
122
+ @address_spec[octet_index].each do |item|
123
+ if item.respond_to? :each
124
+ item.each do |value|
125
+ address.push value
126
+ octet_index == 3 ? yield( address.join '.' ) : recursively_iterate_octets( octet_index + 1, address, &block )
127
+ address.pop
128
+ end
129
+ else
130
+ address.push item
131
+ octet_index == 3 ? yield( address.join '.' ) : recursively_iterate_octets( octet_index + 1, address, &block )
132
+ address.pop
133
+ end
134
+ end
135
+ end
136
+
137
+ def each_address
138
+ if block_given?
139
+ recursively_iterate_octets do |address_str|
140
+ yield address_str
141
+ end
142
+ else
143
+ return to_enum( :each_address ) { size }
144
+ end
145
+ end
146
+
147
+ alias_method :each, :each_address
148
+
149
+ def size
150
+ if @mask_length == 32
151
+ @address_spec.inject(1) { |oc,o| oc * o.inject(0) { |vc,v| vc + ( v.respond_to?(:each) ? v.size : 1 ) } }
152
+ else
153
+ 2**(32-@mask_length)
154
+ end
155
+ end
156
+
157
+ def min
158
+ first
159
+ end
160
+
161
+ def last
162
+ address = nil
163
+ each { |addr| address = addr }
164
+ address
165
+ end
166
+
167
+ def max
168
+ last
169
+ end
170
+
171
+ def minmax
172
+ [first,last]
173
+ end
174
+
175
+ end
176
+
177
+ end
178
+ end
179
+
@@ -0,0 +1,131 @@
1
+
2
+ module Iqeo
3
+ module Hostspec
4
+
5
+ class Runner
6
+
7
+ def self.run args, out: $stdout, err: $stderr
8
+
9
+ if args.empty?
10
+ err.puts "Error: No specs given"
11
+ return 1
12
+ end
13
+
14
+ if args.include?('-h') || args.include?('--help')
15
+ print_help err
16
+ return 0
17
+ end
18
+
19
+ if args.include?('-v') || args.include?('--version')
20
+ print_version err
21
+ return 0
22
+ end
23
+
24
+ cmd_sw_index = args.index('-c') || args.index('--cmd')
25
+ if cmd_sw_index
26
+ specs = args.take(cmd_sw_index) # specs are before switch
27
+ cmd = args.drop(cmd_sw_index+1).join ' ' # command components are after switch, join into string to pass to subshell
28
+ if cmd.empty?
29
+ err.puts "Error: No command given"
30
+ return 2
31
+ end
32
+ else
33
+ specs = args
34
+ cmd = nil
35
+ end
36
+
37
+ results = []
38
+ specs.each do |spec|
39
+ spec = spec.strip
40
+ begin
41
+ host_spec = Hostspec.new spec
42
+ rescue Exception => e
43
+ err.puts "Error: #{e.message}"
44
+ return 3
45
+ end
46
+ if cmd.nil?
47
+ host_spec.each { |address| out.puts address }
48
+ else
49
+ env = {
50
+ 'HOSTSPEC_MASK' => host_spec.mask,
51
+ 'HOSTSPEC_MASKLEN' => host_spec.mask_length.to_s,
52
+ 'HOSTSPEC_COUNT' => host_spec.size.to_s
53
+ }
54
+ host_spec.each_with_index do |address,index|
55
+ env['HOSTSPEC_IP'] = address
56
+ env['HOSTSPEC_INDEX'] = (index+1).to_s
57
+ # this uses posix 'sh', to use bash user's cmd should be 'bash -c "echo \$HOSTSPEC_IP"' and deal with the weird nested quoting
58
+ results << system( env, cmd )
59
+ end
60
+ end
61
+ end
62
+ ( results.empty? || results.all? ) ? 0 : 4
63
+ end
64
+
65
+ def self.print_help io
66
+ io.puts "Usage: hostspec [ options ] specs... [ [ -c / --cmd ] command ]"
67
+ io.puts
68
+ io.puts "Prints all IP addresses for IP host specifications (see Specs:)."
69
+ io.puts "If specified, a command is executed for each IP address, with related values in environment variables (see Command:)."
70
+ io.puts
71
+ io.puts "Specs:"
72
+ io.puts " Nmap-style IP host specifications, multiple specs separated by spaces."
73
+ io.puts " Single host : x.x.x.x or hostname"
74
+ io.puts " Multiple hosts:"
75
+ io.puts " - by mask length : x.x.x.x/m or hostname/m"
76
+ io.puts " - by octet values : x.x.x.a,b,c"
77
+ io.puts " - by octet ranges : x.x.x.d-e"
78
+ io.puts " Octet values and ranges may be combined or applied to any/multiple octets."
79
+ io.puts " Examples:"
80
+ io.puts " hostname : localhost => 127.0.0.1"
81
+ io.puts " hostname w/mask : localhost/24 => 127.0.0.0 127.0.0.1 ... 127.0.0.254 127.0.0.255"
82
+ io.puts " address : 1.1.1.1 => 1.1.1.1"
83
+ io.puts " address w/mask : 2.2.2.1/24 => 2.2.2.0 2.2.2.1 ... 2.2.2.254 2.2.2.255"
84
+ io.puts " address w/values : 3.3.3.10,20,30 => 3.3.3.10 3.3.3.20 3.3.3.30"
85
+ io.puts " address w/ranges : 4.4.4.40-50 => 4.4.4.40 4.4.4.41 ... 4.4.4.49 4.4.4.50"
86
+ io.puts " address w/combo : 5.5.5.2,4-6,8 => 5.5.5.2 5.5.5.4 5.5.5.5 5.5.5.6 5.5.5.8"
87
+ io.puts " complex : 6.1-2,3.4-5.6 => 6.1.4.6 6.1.5.6 6.2.4.6 6.2.5.6 6.3.4.6 6.3.5.6"
88
+ io.puts
89
+ io.puts "Command:"
90
+ io.puts " A command to execute for each IP address may be specified following the command switch ( -c / --cmd )."
91
+ io.puts " The command is executed in a separate shell for each IP address."
92
+ io.puts " Environment variables are provided with values for each IP address command execution."
93
+ io.puts " Quote these variables in the command to prevent substitution by the current shell."
94
+ io.puts " $HOSTSPEC_IP : IP address"
95
+ io.puts " $HOSTSPEC_MASK : Mask (255.255.255.255 if a mask length was not specified)"
96
+ io.puts " $HOSTSPEC_MASKLEN : Mask length (32 if a mask length was not specified)"
97
+ io.puts " $HOSTSPEC_COUNT : Count of IP addresses"
98
+ io.puts " $HOSTSPEC_INDEX : Index of IP address (from 1 to Count)"
99
+ io.puts " Examples:"
100
+ io.puts " Print IP addresses and mask length with index and count:"
101
+ io.puts " hostspec 1.1.1.0/30 --cmd echo '$HOSTSPEC_INDEX' of '$HOSTSPECT_COUNT' : '$HOSTSPEC_IP/$HOSTSPEC_MASKLEN'"
102
+ io.puts " ..."
103
+ io.puts " 1 of 4 : 1.1.1.0/255.255.255.252"
104
+ io.puts " 2 of 4 : 1.1.1.1/255.255.255.252"
105
+ io.puts " 3 of 4 : 1.1.1.2/255.255.255.252"
106
+ io.puts " 4 of 4 : 1.1.1.3/255.255.255.252"
107
+ io.puts " Collect routing tables of all hosts on a network via ssh:"
108
+ io.puts " hostspec 1.1.1.1-254 --cmd 'ssh me@$HOSTSPEC_IP route -n'"
109
+ io.puts " Collect default web pages from all servers on a network via curl:"
110
+ io.puts " hostspec 1.1.1.1-254 --cmd curl -o '$HOSTSPEC_IP.html' 'http://$HOSTSPEC_IP'"
111
+ io.puts " Collect IP configuration info from multiple windows systems (run from a windows system):"
112
+ io.puts " hostspec 1.1.1.1-254 --cmd psexec '\\\\%HOSTSPEC_IP%' ipconfig /all"
113
+ io.puts " Collect IP configuration info from multiple windows systems (run from a linux system with kerberos):"
114
+ io.puts " hostspec 1.1.1.1-254 --cmd winexe --kerberos yes //$(dig -x '$HOSTSPEC_IP' +short) ipconfig /all"
115
+ io.puts " ...or any task that you would have to execute individually on multiple systems."
116
+ io.puts
117
+ io.puts "Options:"
118
+ io.puts " -h / --help : Display this helpful information"
119
+ io.puts " -v / --version : Display program version"
120
+ io.puts
121
+ end
122
+
123
+ def self.print_version io
124
+ io.puts "hostspec version #{Iqeo::Hostspec::VERSION}"
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+ end
131
+
@@ -0,0 +1,7 @@
1
+
2
+ module Iqeo
3
+ module Hostspec
4
+ VERSION = '0.1.0.pre2'
5
+ end
6
+ end
7
+
data/lib/iqeo/hostspec.rb CHANGED
@@ -1,180 +1,5 @@
1
- require 'resolv'
2
-
3
- module Iqeo
4
-
5
- class HostspecException < Exception ; end
6
-
7
- class Hostspec
8
-
9
- VERSION = '0.1.0.pre1'
10
-
11
- include Enumerable
12
-
13
- attr_reader :string, :mask, :mask_length, :address_spec, :hostname
14
-
15
- def initialize spec_str
16
- @string = spec_str
17
- raise HostspecException, 'spec cannot be empty' if spec_str.empty?
18
- host_str, mask_str = split_on_slash spec_str
19
- raise HostspecException, 'host cannot be empty' if host_str.empty?
20
- parse_mask mask_str
21
- begin
22
- parse_address_spec host_str
23
- rescue HostspecException
24
- parse_hostname host_str
25
- end
26
- raise HostspecException, 'complex spec cannot have mask length' if @mask_specified && @address_spec.any? { |octet| octet.size > 1 }
27
- mask_address_spec
28
- end
29
-
30
- def split_on_slash str
31
- case str.count '/'
32
- when 0 then [ str.strip, '' ]
33
- when 1 then str.strip.split '/'
34
- else raise 'bad format, expected 0 or 1 "/"'
35
- end
36
- end
37
-
38
- def parse_mask str
39
- if str.empty?
40
- @mask = '255.255.255.255'
41
- @mask_length = 32
42
- @mask_specified = false
43
- return
44
- end
45
- if match = str.match( /^\d+$/ )
46
- @mask_length = match[0].to_i
47
- raise "bad mask length (#{@mask_length}), expected between 0 ad 32" unless @mask_length.between? 0,32
48
- mask_int = ((2**@mask_length)-1) << (32-@mask_length)
49
- @mask = [24,16,8,0].collect { |n| ( mask_int & ( 255 << n ) ) >> n }.join '.'
50
- @mask_specified = true
51
- else
52
- raise "bad format, expected mask length after '/'"
53
- end
54
- end
55
-
56
- def mask_address_spec
57
- @address_spec.each_with_index do |octet,index|
58
- high_bit_position = ( index * 8 ) + 1
59
- low_bit_position = ( index + 1 ) * 8
60
- @address_spec[index] = case
61
- when @mask_length >= low_bit_position then octet
62
- when @mask_length < high_bit_position then [0..255]
63
- else
64
- octet_mask_length = @mask_length % 8
65
- octet_mask = ( ( 2 ** octet_mask_length ) - 1 ) << ( 8 - octet_mask_length )
66
- octet_mask_inverted = octet_mask ^ 255
67
- octet_min = octet_mask & octet[0]
68
- octet_max = octet_min | octet_mask_inverted
69
- [octet_min..octet_max]
70
- end
71
- end
72
- end
73
-
74
- def parse_address_spec str
75
- octet_strs = str.split '.'
76
- raise HostspecException, 'bad ip, expected 4 octets' unless octet_strs.size == 4
77
- octets = octet_strs.collect { |octet_str| parse_octet octet_str }
78
- @address_spec = octets
79
- end
80
-
81
- def parse_octet str
82
- values = str.split ','
83
- values.collect { |value_str| parse_octet_value value_str }
84
- end
85
-
86
- def parse_octet_value str
87
- # values may be dash denoted ranges, possibilities...
88
- # n : just a number : 'n'.split '-' == ['n'] <= same = problem! 'n'.split '-', -1 == [ "n" ]
89
- # n-m : range from n to m : 'n-m'.split '-' == ['n','m'] 'n-m'.split '-', -1 == [ "n" , "m" ]
90
- # n- : range from n to 255 : 'n-'.split '-' == ['n'] <= same = problem! 'n-'.split '-', -1 == [ "n" , "" ]
91
- # -m : range from 0 to m : '-m'.split '-' == ['','m'] '-m'.split '-', -1 == [ "" , "m" ]
92
- # - : range from 0 to 255 : '-'.split '-' == [] '-'.split '-', -1 == [ "" , "" ]
93
- numbers = str.split '-', -1 # maximize return fields to distinguish 'n' from '-m'
94
- case numbers.size
95
- when 1 then
96
- check_octet_value numbers[0]
97
- numbers[0].to_i
98
- when 2 then
99
- numbers[0] = '0' if numbers[0].empty?
100
- numbers[1] = '255' if numbers[1].empty?
101
- check_octet_value numbers[0]
102
- check_octet_value numbers[1]
103
- range_start = numbers[0].to_i
104
- range_finish = numbers[1].to_i
105
- raise HostspecException, "bad ip, reversed range in octet value: #{str}" if range_start > range_finish
106
- range_start..range_finish
107
- else
108
- raise HostspecException, "bad ip, invalid octet value: #{str}"
109
- end
110
- end
111
-
112
- def check_octet_value str
113
- match = str.match /^(25[0-5]|2[0-4]\d|[0-1]\d\d|\d\d|\d)$/
114
- raise HostspecException, "bad ip, octet value is not a number in 0-255: #{str}" unless match
115
- end
116
-
117
- def parse_hostname str
118
- @hostname = str
119
- parse_address_spec Resolv.getaddress(str)
120
- end
121
-
122
- def recursively_iterate_octets octet_index = 0, address = [], &block
123
- @address_spec[octet_index].each do |item|
124
- if item.respond_to? :each
125
- item.each do |value|
126
- address.push value
127
- octet_index == 3 ? yield( address.join '.' ) : recursively_iterate_octets( octet_index + 1, address, &block )
128
- address.pop
129
- end
130
- else
131
- address.push item
132
- octet_index == 3 ? yield( address.join '.' ) : recursively_iterate_octets( octet_index + 1, address, &block )
133
- address.pop
134
- end
135
- end
136
- end
137
-
138
- def each_address
139
- if block_given?
140
- recursively_iterate_octets do |address_str|
141
- yield address_str
142
- end
143
- else
144
- return to_enum( :each_address ) { size }
145
- end
146
- end
147
-
148
- alias_method :each, :each_address
149
-
150
- def size
151
- if @mask_length == 32
152
- @address_spec.inject(1) { |oc,o| oc * o.inject(0) { |vc,v| vc + ( v.respond_to?(:each) ? v.size : 1 ) } }
153
- else
154
- 2**(32-@mask_length)
155
- end
156
- end
157
-
158
- def min
159
- first
160
- end
161
-
162
- def last
163
- address = nil
164
- each { |addr| address = addr }
165
- address
166
- end
167
-
168
- def max
169
- last
170
- end
171
-
172
- def minmax
173
- [first,last]
174
- end
175
-
176
- end
177
-
178
- end
179
1
 
2
+ require 'iqeo/hostspec/version'
3
+ require 'iqeo/hostspec/hostspec'
4
+ require 'iqeo/hostspec/runner'
180
5
 
@@ -1,21 +1,22 @@
1
1
  require 'iqeo/hostspec'
2
+ include Iqeo::Hostspec
2
3
 
3
- describe Iqeo::Hostspec do
4
+ describe Hostspec do
4
5
 
5
6
  context '.new' do
6
7
 
7
8
  it 'accepts a single argument only' do
8
- expect { Iqeo::Hostspec.new }.to raise_error
9
- expect { Iqeo::Hostspec.new "1.1.1.1" }.to_not raise_error
10
- expect { Iqeo::Hostspec.new "1.1.1.1","2.2.2.2" }.to raise_error
9
+ expect { Hostspec.new }.to raise_error
10
+ expect { Hostspec.new "1.1.1.1" }.to_not raise_error
11
+ expect { Hostspec.new "1.1.1.1","2.2.2.2" }.to raise_error
11
12
  end
12
13
 
13
14
  it 'does not require a mask length' do
14
- expect { Iqeo::Hostspec.new "3.3.3.3" }.to_not raise_error
15
+ expect { Hostspec.new "3.3.3.3" }.to_not raise_error
15
16
  end
16
17
 
17
18
  it 'defaults to a host mask when none specified' do
18
- hs = Iqeo::Hostspec.new "4.4.4.4"
19
+ hs = Hostspec.new "4.4.4.4"
19
20
  hs.mask.should eq '255.255.255.255'
20
21
  hs.mask_length.should eq 32
21
22
  end
@@ -50,27 +51,27 @@ describe Iqeo::Hostspec do
50
51
  end
51
52
 
52
53
  it 'is specified numerically after /' do
53
- expect { Iqeo::Hostspec.new "5.5.5.5/24" }.to_not raise_error
54
- expect { Iqeo::Hostspec.new "5.5.5.5/" }.to raise_error
55
- expect { Iqeo::Hostspec.new "5.5.5.5/xyz" }.to raise_error
54
+ expect { Hostspec.new "5.5.5.5/24" }.to_not raise_error
55
+ expect { Hostspec.new "5.5.5.5/" }.to raise_error
56
+ expect { Hostspec.new "5.5.5.5/xyz" }.to raise_error
56
57
  end
57
58
 
58
59
  it 'is between 0 and 32' do
59
60
  (0..32).each do |masklen|
60
- hs = Iqeo::Hostspec.new "1.2.3.4/#{masklen}"
61
+ hs = Hostspec.new "1.2.3.4/#{masklen}"
61
62
  hs.mask_length.should eq masklen
62
63
  end
63
64
  end
64
65
 
65
66
  it 'cannot be > 32' do
66
67
  [ 33, 100, 1000 ].each do |masklen|
67
- expect { Iqeo::Hostspec.new "1.2.3.4/#{masklen}" }.to raise_error
68
+ expect { Hostspec.new "1.2.3.4/#{masklen}" }.to raise_error
68
69
  end
69
70
  end
70
71
 
71
72
  it 'sets mask string' do
72
73
  @mask_strings.each_with_index do |str,len|
73
- hs = Iqeo::Hostspec.new "1.2.3.4/#{len}"
74
+ hs = Hostspec.new "1.2.3.4/#{len}"
74
75
  hs.mask.should eq str
75
76
  end
76
77
  end
@@ -84,8 +85,8 @@ describe Iqeo::Hostspec do
84
85
  end
85
86
 
86
87
  it 'cannot be empty' do
87
- expect { Iqeo::Hostspec.new '' }.to raise_error
88
- expect { Iqeo::Hostspec.new '/32' }.to raise_error
88
+ expect { Hostspec.new '' }.to raise_error
89
+ expect { Hostspec.new '/32' }.to raise_error
89
90
  end
90
91
 
91
92
  context 'an IP address' do
@@ -93,7 +94,7 @@ describe Iqeo::Hostspec do
93
94
  it 'may specify a single host without a mask length' do
94
95
  @octets.each_cons(4) do |octets|
95
96
  address = octets.join('.')
96
- hs = Iqeo::Hostspec.new address
97
+ hs = Hostspec.new address
97
98
  hs.address_spec.collect(&:first).should eq octets
98
99
  end
99
100
  end
@@ -107,7 +108,7 @@ describe Iqeo::Hostspec do
107
108
  '1.1.1.1/0' => [[0..255],[0..255],[0..255],[0..255]]
108
109
  }
109
110
  slash_specs.each do |spec_str,spec_data|
110
- hs = Iqeo::Hostspec.new spec_str
111
+ hs = Hostspec.new spec_str
111
112
  hs.address_spec.should eq spec_data
112
113
  end
113
114
  end
@@ -248,7 +249,7 @@ describe Iqeo::Hostspec do
248
249
  '255.255.255.255/0' => [[0..255] ,[0..255],[0..255],[0..255]],
249
250
  }
250
251
  slash_specs.each do |spec_str,spec_data|
251
- hs = Iqeo::Hostspec.new spec_str
252
+ hs = Hostspec.new spec_str
252
253
  hs.address_spec.should eq spec_data
253
254
  end
254
255
  end
@@ -258,16 +259,16 @@ describe Iqeo::Hostspec do
258
259
  context 'a hostname' do
259
260
 
260
261
  it 'is assumed when not a simple IP spec' do
261
- Iqeo::Hostspec.new('localhost').hostname.should eq 'localhost'
262
+ Hostspec.new('localhost.iqeo.net').hostname.should eq 'localhost.iqeo.net'
262
263
  end
263
264
 
264
265
  it 'is assumed when not a complex IP spec' do
265
- expect { hs = Iqeo::Hostspec.new "1.2.3.100-300" }.to raise_error Resolv::ResolvError
266
+ expect { hs = Hostspec.new "1.2.3.100-300" }.to raise_error Resolv::ResolvError
266
267
  end
267
268
 
268
269
  it 'resolves to a host IP address' do
269
- hs = Iqeo::Hostspec.new('localhost')
270
- hs.hostname.should eq 'localhost'
270
+ hs = Hostspec.new('localhost.iqeo.net')
271
+ hs.hostname.should eq 'localhost.iqeo.net'
271
272
  hs.address_spec.collect(&:first).should eq [127,0,0,1]
272
273
  hs.mask.should eq '255.255.255.255'
273
274
  hs.mask_length.should eq 32
@@ -275,7 +276,7 @@ describe Iqeo::Hostspec do
275
276
 
276
277
  it 'may specify address range with a mask length' do
277
278
  (0..32).each do |masklen|
278
- hs = Iqeo::Hostspec.new("localhost/#{masklen}")
279
+ hs = Hostspec.new("localhost.iqeo.net/#{masklen}")
279
280
  hs.mask_length.should eq masklen
280
281
  end
281
282
  end
@@ -286,51 +287,51 @@ describe Iqeo::Hostspec do
286
287
 
287
288
  it 'must not specify a mask length' do
288
289
  (0..32).each do |masklen|
289
- expect { hs = Iqeo::Hostspec.new "10.20,22,24.30-39.40/#{masklen}" }.to raise_error
290
+ expect { hs = Hostspec.new "10.20,22,24.30-39.40/#{masklen}" }.to raise_error
290
291
  end
291
292
  end
292
293
 
293
294
  it 'may specify octet values with commas' do
294
- hs = Iqeo::Hostspec.new '10,11,12.20,21,22.30,31,32.40,41,42'
295
+ hs = Hostspec.new '10,11,12.20,21,22.30,31,32.40,41,42'
295
296
  hs.address_spec.should eq [[10,11,12],[20,21,22],[30,31,32],[40,41,42]]
296
297
  end
297
298
 
298
299
  context 'may specify octet value ranges with dashes' do
299
300
 
300
301
  it 'in form "n-m"' do
301
- hs = Iqeo::Hostspec.new '10-19.20-29.30-39.40-49'
302
+ hs = Hostspec.new '10-19.20-29.30-39.40-49'
302
303
  hs.address_spec.should eq [[10..19],[20..29],[30..39],[40..49]]
303
304
  end
304
305
 
305
306
  it 'in form "n-"' do
306
- hs = Iqeo::Hostspec.new '10-.20-.30-.40-'
307
+ hs = Hostspec.new '10-.20-.30-.40-'
307
308
  hs.address_spec.should eq [[10..255],[20..255],[30..255],[40..255]]
308
309
  end
309
310
 
310
311
  it 'in form "-m"' do
311
- hs = Iqeo::Hostspec.new '-19.-29.-39.-49'
312
+ hs = Hostspec.new '-19.-29.-39.-49'
312
313
  hs.address_spec.should eq [[0..19],[0..29],[0..39],[0..49]]
313
314
  end
314
315
 
315
316
  it 'in form "-"' do
316
- hs = Iqeo::Hostspec.new '-.-.-.-'
317
+ hs = Hostspec.new '-.-.-.-'
317
318
  hs.address_spec.should eq [[0..255],[0..255],[0..255],[0..255]]
318
319
  end
319
320
 
320
321
  end
321
322
 
322
323
  it 'may mix octet specifications with dashes and commas' do
323
- hs = Iqeo::Hostspec.new '1,10,100,200.13-247.23-.-99'
324
+ hs = Hostspec.new '1,10,100,200.13-247.23-.-99'
324
325
  hs.address_spec.should eq [[1,10,100,200],[13..247],[23..255],[0..99]]
325
326
  end
326
327
 
327
328
  it 'may combine octet specification with dashes and commas' do
328
- hs = Iqeo::Hostspec.new '0,1,10,100-200,250,254,255.-50,99,200-.-33,44,55-66,77,88-.-'
329
+ hs = Hostspec.new '0,1,10,100-200,250,254,255.-50,99,200-.-33,44,55-66,77,88-.-'
329
330
  hs.address_spec.should eq [[0,1,10,100..200,250,254,255],[0..50,99,200..255],[0..33,44,55..66,77,88..255],[0..255]]
330
331
  end
331
332
 
332
333
  it 'may not specifiy a reversed range' do
333
- expect { hs = Iqeo::Hostspec.new '1.1.1.20-10' }.to raise_error
334
+ expect { hs = Hostspec.new '1.1.1.20-10' }.to raise_error
334
335
  end
335
336
 
336
337
  end
@@ -374,7 +375,7 @@ describe Iqeo::Hostspec do
374
375
  it 'a single address for host address' do
375
376
  @octets.each_cons(4) do |octets|
376
377
  address = octets.join('.')
377
- hs = Iqeo::Hostspec.new address
378
+ hs = Hostspec.new address
378
379
  address_count = 0
379
380
  hs.send(method) do |address_str|
380
381
  address_str.should eq address
@@ -385,7 +386,7 @@ describe Iqeo::Hostspec do
385
386
  end
386
387
 
387
388
  it 'multiple addresses for a spec with multiple octet values (commas)' do
388
- hs = Iqeo::Hostspec.new @multi_spec_with_commas
389
+ hs = Hostspec.new @multi_spec_with_commas
389
390
  address_count = 0
390
391
  hs.send(method) do |address_str|
391
392
  address_str.should eq @multi_expected_addresses[address_count]
@@ -395,7 +396,7 @@ describe Iqeo::Hostspec do
395
396
  end
396
397
 
397
398
  it 'multiple addresses for a spec with octet ranges (dashes)' do
398
- hs = Iqeo::Hostspec.new @multi_spec_with_dashes
399
+ hs = Hostspec.new @multi_spec_with_dashes
399
400
  address_count = 0
400
401
  hs.send(method) do |address_str|
401
402
  address_str.should eq @multi_expected_addresses[address_count]
@@ -406,7 +407,7 @@ describe Iqeo::Hostspec do
406
407
 
407
408
  it 'masked addresses for specs with a mask length' do
408
409
  @specs_with_slash.each do |address_spec,expected_addresses|
409
- hs = Iqeo::Hostspec.new address_spec
410
+ hs = Hostspec.new address_spec
410
411
  address_count = 0
411
412
  hs.send(method) do |address_str|
412
413
  address_str.should eq expected_addresses[address_count]
@@ -429,59 +430,59 @@ describe Iqeo::Hostspec do
429
430
  context 'enumerable' do
430
431
 
431
432
  it '.each returns an Enumerator' do
432
- hs = Iqeo::Hostspec.new '10.20.30.40/24'
433
+ hs = Hostspec.new '10.20.30.40/24'
433
434
  hs.each.class.should eq Enumerator
434
435
  end
435
436
 
436
437
  it 'responds to Enumerable methods' do
437
- hs = Iqeo::Hostspec.new '10.20.30.40/24'
438
+ hs = Hostspec.new '10.20.30.40/24'
438
439
  hs.all? { |i| i.start_with? '10' }.should be_true
439
440
  hs.any? { |i| i.end_with? '255' }.should be_true
440
441
  end
441
442
 
442
443
  it 'can calculate size for simple specs' do
443
444
  (0..32).each do |masklen|
444
- hs = Iqeo::Hostspec.new "10.20.30.40/#{masklen}"
445
+ hs = Hostspec.new "10.20.30.40/#{masklen}"
445
446
  hs.size.should eq 2**(32-masklen)
446
447
  end
447
448
  end
448
449
 
449
450
  it 'can calculate size for complex specs' do
450
- hs = Iqeo::Hostspec.new @multi_spec_with_commas
451
+ hs = Hostspec.new @multi_spec_with_commas
451
452
  hs.size.should eq @multi_expected_addresses.size
452
- hs = Iqeo::Hostspec.new @multi_spec_with_dashes
453
+ hs = Hostspec.new @multi_spec_with_dashes
453
454
  hs.size.should eq @multi_expected_addresses.size
454
455
  end
455
456
 
456
457
  it 'Enumerator can make use of size' do
457
- hs = Iqeo::Hostspec.new '1.1.1.1-10'
458
+ hs = Hostspec.new '1.1.1.1-10'
458
459
  hs.size.should eq 10
459
460
  enumerator = hs.each
460
461
  enumerator.size.should eq 10
461
462
  end
462
463
 
463
464
  it 'has first (from enumerable)' do
464
- hs = Iqeo::Hostspec.new '1.1.2-10.20-100'
465
+ hs = Hostspec.new '1.1.2-10.20-100'
465
466
  hs.first.should eq '1.1.2.20'
466
467
  end
467
468
 
468
469
  it 'has last' do
469
- hs = Iqeo::Hostspec.new '1.1.2-10.20-100'
470
+ hs = Hostspec.new '1.1.2-10.20-100'
470
471
  hs.last.should eq '1.1.10.100'
471
472
  end
472
473
 
473
474
  it 'has min equals first' do
474
- hs = Iqeo::Hostspec.new '1.1.2-10.20-100'
475
+ hs = Hostspec.new '1.1.2-10.20-100'
475
476
  hs.min.should eq '1.1.2.20'
476
477
  end
477
478
 
478
479
  it 'has max equals last' do
479
- hs = Iqeo::Hostspec.new '1.1.2-10.20-100'
480
+ hs = Hostspec.new '1.1.2-10.20-100'
480
481
  hs.max.should eq '1.1.10.100'
481
482
  end
482
483
 
483
484
  it 'has minmax' do
484
- hs = Iqeo::Hostspec.new '1.1.2-10.20-100'
485
+ hs = Hostspec.new '1.1.2-10.20-100'
485
486
  hs.minmax.should eq ['1.1.2.20','1.1.10.100']
486
487
  end
487
488
 
@@ -1,4 +1,5 @@
1
1
  load './bin/hostspec'
2
+ include Iqeo::Hostspec
2
3
 
3
4
  require 'stringio'
4
5
 
@@ -15,13 +16,13 @@ describe Runner do
15
16
  Runner.run( ['10.20.30.40'], out: StringIO.new, err: StringIO.new ).should eq 0
16
17
  Runner.run( ['10.20.30.40/24'], out: StringIO.new, err: StringIO.new ).should eq 0
17
18
  Runner.run( ['10,11.20-29.30,31-38,39.40'], out: StringIO.new, err: StringIO.new ).should eq 0
18
- Runner.run( ['localhost'], out: StringIO.new, err: StringIO.new ).should eq 0
19
+ Runner.run( ['localhost.iqeo.net'], out: StringIO.new, err: StringIO.new ).should eq 0
19
20
  end
20
21
 
21
22
  it 'accepts multiple host specs for arguments with no switches' do
22
23
  Runner.run( ['10.20.30.40/24','11.22.33.44'], out: StringIO.new, err: StringIO.new ).should eq 0
23
24
  Runner.run( ['10,11.20-29.30,31-38,39.40','11.22.33.44/28'], out: StringIO.new, err: StringIO.new ).should eq 0
24
- Runner.run( ['localhost','1.2.3.4/26','11.22.33.44-55'], out: StringIO.new, err: StringIO.new ).should eq 0
25
+ Runner.run( ['localhost.iqeo.net','1.2.3.4/26','11.22.33.44-55'], out: StringIO.new, err: StringIO.new ).should eq 0
25
26
  end
26
27
 
27
28
  it 'expects valid single host spec or prints error' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iqeo-hostspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre1
4
+ version: 0.1.0.pre2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerard Fowley
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-11 00:00:00.000000000 Z
11
+ date: 2014-02-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -33,6 +33,7 @@ extensions: []
33
33
  extra_rdoc_files: []
34
34
  files:
35
35
  - ".gitignore"
36
+ - ".travis.yml"
36
37
  - Gemfile
37
38
  - Guardfile
38
39
  - LICENSE.txt
@@ -41,8 +42,11 @@ files:
41
42
  - bin/hostspec
42
43
  - iqeo-hostspec.gemspec
43
44
  - lib/iqeo/hostspec.rb
44
- - spec/hostspec_runner_spec.rb
45
+ - lib/iqeo/hostspec/hostspec.rb
46
+ - lib/iqeo/hostspec/runner.rb
47
+ - lib/iqeo/hostspec/version.rb
45
48
  - spec/iqeo/hostspec_spec.rb
49
+ - spec/iqeo/runner_spec.rb
46
50
  - tmux
47
51
  homepage: ''
48
52
  licenses:
@@ -69,5 +73,5 @@ signing_key:
69
73
  specification_version: 4
70
74
  summary: Write a gem summary
71
75
  test_files:
72
- - spec/hostspec_runner_spec.rb
73
76
  - spec/iqeo/hostspec_spec.rb
77
+ - spec/iqeo/runner_spec.rb