iqeo-hostspec 0.1.0.pre1
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 +7 -0
- data/.gitignore +18 -0
- data/Gemfile +7 -0
- data/Guardfile +13 -0
- data/LICENSE.txt +675 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/bin/hostspec +131 -0
- data/iqeo-hostspec.gemspec +24 -0
- data/lib/iqeo/hostspec.rb +180 -0
- data/spec/hostspec_runner_spec.rb +109 -0
- data/spec/iqeo/hostspec_spec.rb +493 -0
- data/tmux +38 -0
- metadata +73 -0
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Iqeo::Hostspec
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'iqeo-hostspec'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install iqeo-hostspec
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
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/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/hostspec
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'iqeo/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 = 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
|
131
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib/', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'iqeo/hostspec'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
|
8
|
+
spec.name = "iqeo-hostspec"
|
9
|
+
spec.version = Iqeo::Hostspec::VERSION
|
10
|
+
spec.authors = ["Gerard Fowley"]
|
11
|
+
spec.email = ["gerard.fowley@iqeo.net"]
|
12
|
+
spec.description = %q{Write a gem description}
|
13
|
+
spec.summary = %q{Write a gem summary}
|
14
|
+
spec.homepage = ""
|
15
|
+
spec.license = "GPLv3"
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($/)
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "rspec"
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,180 @@
|
|
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
|
+
|
180
|
+
|
@@ -0,0 +1,109 @@
|
|
1
|
+
load './bin/hostspec'
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
describe Runner do
|
6
|
+
|
7
|
+
it 'expects an array of arguments' do
|
8
|
+
expect { Runner.run }.to raise_error
|
9
|
+
expect { Runner.run nil }.to raise_error
|
10
|
+
expect { Runner.run '1.1.1.1' }.to raise_error
|
11
|
+
expect { Runner.run [], out: StringIO.new, err: StringIO.new }.to_not raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'accepts single host spec for argument with no switches' do
|
15
|
+
Runner.run( ['10.20.30.40'], out: StringIO.new, err: StringIO.new ).should eq 0
|
16
|
+
Runner.run( ['10.20.30.40/24'], out: StringIO.new, err: StringIO.new ).should eq 0
|
17
|
+
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
|
+
end
|
20
|
+
|
21
|
+
it 'accepts multiple host specs for arguments with no switches' do
|
22
|
+
Runner.run( ['10.20.30.40/24','11.22.33.44'], out: StringIO.new, err: StringIO.new ).should eq 0
|
23
|
+
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
|
+
end
|
26
|
+
|
27
|
+
it 'expects valid single host spec or prints error' do
|
28
|
+
error = StringIO.new
|
29
|
+
Runner.run( ['no-such-host/xyz'], out: StringIO.new, err: error ).should eq 3
|
30
|
+
error.string.should include 'Error'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'expects valid multiple host specs or prints error' do
|
34
|
+
error = StringIO.new
|
35
|
+
Runner.run( ['no-such-host/xyz','5.5.5.5'], out: StringIO.new, err: error ).should eq 3
|
36
|
+
error.string.should include 'Error'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'lists single host spec IP addresses to stdout for no command (-c/--cmd) switch' do
|
40
|
+
output = StringIO.new
|
41
|
+
Runner.run( ['1.1.1.1-5'], out: output )
|
42
|
+
output.string.should eq "1.1.1.1\n1.1.1.2\n1.1.1.3\n1.1.1.4\n1.1.1.5\n"
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'lists multiple host spec IP addresses to stdout for no command (-c/--cmd) switch' do
|
46
|
+
output = StringIO.new
|
47
|
+
Runner.run( ['1.1.1.1-5','2.2.2.4,6,8'], out: output )
|
48
|
+
output.string.should eq "1.1.1.1\n1.1.1.2\n1.1.1.3\n1.1.1.4\n1.1.1.5\n2.2.2.4\n2.2.2.6\n2.2.2.8\n"
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'accepts host specs for arguments before command (-c/--cmd) switch' do
|
52
|
+
Runner.run( ['1.1.1.1','2.2.2.2','-c' ,'#'], out: StringIO.new, err: StringIO.new ).should eq 0
|
53
|
+
Runner.run( ['1.1.1.1','2.2.2.2','--cmd','#'], out: StringIO.new, err: StringIO.new ).should eq 0
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'expects a command argument after command (-c/--cmd) switch or prints error' do
|
57
|
+
[['-c'],['-c',nil],['-c',''],['--cmd'],['--cmd',nil],['--cmd','']].each do |args|
|
58
|
+
error = StringIO.new
|
59
|
+
Runner.run( ['1.1.1.1','2.2.2.2',*args], out: StringIO.new, err: error ).should eq 2
|
60
|
+
error.string.should include 'Error'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'prints an error message to stderr for no arguments' do
|
65
|
+
error = StringIO.new
|
66
|
+
Runner.run( [], out: StringIO.new, err: error ).should eq 1
|
67
|
+
error.string.should include 'Error'
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'prints a helpful message to stderr for help (-h/--help) switch' do
|
71
|
+
[['-h'],['1.2.3.4','-h'],['-h','1.2.3.4'],['1.2.3.4','-h','5.6.7.8'],['--help'],['1.2.3.4','--help'],['--help','1.2.3.4'],['1.2.3.4','--help','5.6.7.8']].each do |args|
|
72
|
+
error = StringIO.new
|
73
|
+
Runner.run( args, out: StringIO.new, err: error ).should eq 0
|
74
|
+
error.string.should include 'hostspec'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'prints version info to stderr for version (-v/--version) switch' do
|
79
|
+
[['-v'],['1.2.3.4','-v'],['-v','1.2.3.4'],['1.2.3.4','-v','5.6.7.8'],['--version'],['1.2.3.4','--version'],['--version','1.2.3.4'],['1.2.3.4','--version','5.6.7.8']].each do |args|
|
80
|
+
error = StringIO.new
|
81
|
+
Runner.run( args, out: StringIO.new, err: error ).should eq 0
|
82
|
+
error.string.should include 'hostspec'
|
83
|
+
error.string.should include Iqeo::Hostspec::VERSION
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'executes the argument after the command (-c/--cmd) switch as a shell command' do
|
88
|
+
File.delete 'test.txt' if File.exists? 'test.txt'
|
89
|
+
Runner.run( ['1.1.1.1-3','2.2.2.1-3','-c','echo X >> test.txt'] ).should eq 0
|
90
|
+
File.read('test.txt').should eq "X\n" * 6
|
91
|
+
File.delete 'test.txt' if File.exists? 'test.txt'
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'provides hostspec values to command via environment variables $HOSTSPEC_IP, $HOSTSPEC_MASK, $HOSTSPEC_MASKLEN, $HOSTSPEC_COUNT, $HOSTSPEC_INDEX' do
|
95
|
+
File.delete 'test.txt' if File.exists? 'test.txt'
|
96
|
+
Runner.run( ['1.1.1.1-3','2.2.2.1-3','-c','echo $HOSTSPEC_IP $HOSTSPEC_MASK $HOSTSPEC_MASKLEN $HOSTSPEC_COUNT $HOSTSPEC_INDEX >> test.txt'] )
|
97
|
+
File.read('test.txt').should eq "1.1.1.1 255.255.255.255 32 3 1\n1.1.1.2 255.255.255.255 32 3 2\n1.1.1.3 255.255.255.255 32 3 3\n2.2.2.1 255.255.255.255 32 3 1\n2.2.2.2 255.255.255.255 32 3 2\n2.2.2.3 255.255.255.255 32 3 3\n"
|
98
|
+
File.delete 'test.txt' if File.exists? 'test.txt'
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'returns 0 on successful execution of commands' do
|
102
|
+
Runner.run( ['1.1.1.1-10','-c','echo'] ).should eq 0
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'returns 4 on failure of execution of any command' do
|
106
|
+
Runner.run( ['1.1.1.1-10','-c',"no-way-this-is-a-valid-command-#{rand(99999999)}"] ).should eq 4
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|