iqeo-hostspec 0.1.0.pre1 → 0.1.0.pre2

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