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 +4 -4
- data/.travis.yml +5 -0
- data/README.md +3 -7
- data/bin/hostspec +1 -126
- data/lib/iqeo/hostspec/hostspec.rb +179 -0
- data/lib/iqeo/hostspec/runner.rb +131 -0
- data/lib/iqeo/hostspec/version.rb +7 -0
- data/lib/iqeo/hostspec.rb +3 -178
- data/spec/iqeo/hostspec_spec.rb +47 -46
- data/spec/{hostspec_runner_spec.rb → iqeo/runner_spec.rb} +3 -2
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b3e2f1c15026a95d95666c9eaee3de460ccf92d
|
4
|
+
data.tar.gz: 263459817766d46b9b32d4bfee19f546134817b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0be30e4d2da2a60e74561d4a224e8cf85c4f3d5477ede05859aa6a62f58983122d907489920071acd200bd19dfd53bef9ecd3594b576354ac102b61b70436404
|
7
|
+
data.tar.gz: a07aabde5df2381c9d2f4b379a16e8c758c422c773e16d665b3d2af1ff84ef2b85876cd0c54fb55a72d2e87512782d5212847735fd216f4b2bac1c64acc66904
|
data/.travis.yml
ADDED
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
|
-
|
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
|
+
|
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
|
|
data/spec/iqeo/hostspec_spec.rb
CHANGED
@@ -1,21 +1,22 @@
|
|
1
1
|
require 'iqeo/hostspec'
|
2
|
+
include Iqeo::Hostspec
|
2
3
|
|
3
|
-
describe
|
4
|
+
describe Hostspec do
|
4
5
|
|
5
6
|
context '.new' do
|
6
7
|
|
7
8
|
it 'accepts a single argument only' do
|
8
|
-
expect {
|
9
|
-
expect {
|
10
|
-
expect {
|
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 {
|
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 =
|
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 {
|
54
|
-
expect {
|
55
|
-
expect {
|
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 =
|
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 {
|
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 =
|
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 {
|
88
|
-
expect {
|
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 =
|
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 =
|
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 =
|
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
|
-
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
451
|
+
hs = Hostspec.new @multi_spec_with_commas
|
451
452
|
hs.size.should eq @multi_expected_addresses.size
|
452
|
-
hs =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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.
|
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
|
+
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
|
-
-
|
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
|