hostsfile 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Guardfile +1 -1
- data/README.md +17 -13
- data/hostsfile.gemspec +12 -4
- data/lib/hostsfile/entry.rb +57 -60
- data/lib/hostsfile/manipulator.rb +144 -112
- data/lib/hostsfile/version.rb +1 -1
- data/spec/entry_spec.rb +43 -0
- data/spec/fixtures/sample_hosts +10 -0
- data/spec/hostsfile_spec.rb +3 -69
- data/spec/manipulator_spec.rb +202 -0
- data/spec/spec_helper.rb +34 -0
- metadata +80 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0df9590599c111715cca1d3e873ccf494a58b6ba
|
4
|
+
data.tar.gz: ce025bcf57210c92e589356129cc775dbdcb55dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d3bbefaae49d7797e16e1b9361e301447c0b1e591d0fcc87e387acc131b26dc64fc17377088899adc3d22f95b7aa352f7d3bd45224fc0521588be78883fb4c0
|
7
|
+
data.tar.gz: 4c12713032af45faffd8f0cdb3c5940aee31e053938830cced802856455fef8b22bddc6f13f155c11d784d04beef3c5f49bdf82d622543d7fdb2a652b2c22127
|
data/Guardfile
CHANGED
@@ -11,7 +11,7 @@
|
|
11
11
|
# * 'just' rspec: 'rspec'
|
12
12
|
guard :rspec, cmd: 'bundle exec rspec' do
|
13
13
|
watch(%r{^spec/.+_spec\.rb$})
|
14
|
-
watch(%r{^lib/(
|
14
|
+
watch(%r{^lib/([^/]+).*\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
15
15
|
watch('spec/spec_helper.rb') { "spec" }
|
16
16
|
end
|
17
17
|
|
data/README.md
CHANGED
@@ -1,24 +1,22 @@
|
|
1
1
|
# Hostsfile
|
2
2
|
|
3
|
-
|
3
|
+
[![Build Status](http://img.shields.io/travis/tnarik/hostsfile.svg)](https://travis-ci.org/tnarik/hostsfile)
|
4
|
+
[![Code Climate](http://img.shields.io/codeclimate/github/tnarik/hostsfile.svg)](https://codeclimate.com/github/tnarik/hostsfile)
|
5
|
+
[![Coveralls](http://img.shields.io/coveralls/tnarik/hostsfile.svg)](https://coveralls.io/r/tnarik/hostsfile)
|
6
|
+
[![RubyGems](http://img.shields.io/gem/v/hostsfile.svg)](http://rubygems.org/gems/hostsfile)
|
7
|
+
[![Gemnasium](http://img.shields.io/gemnasium/tnarik/hostsfile.svg)](https://gemnasium.com/tnarik/hostsfile)
|
4
8
|
|
5
9
|
## Installation
|
6
10
|
|
7
|
-
|
11
|
+
Install via Rubygems or Gemfile
|
8
12
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
$ bundle
|
14
|
-
|
15
|
-
Or install it yourself as:
|
16
|
-
|
17
|
-
$ gem install hostsfile
|
13
|
+
```zsh
|
14
|
+
$ gem install hostsfile
|
15
|
+
```
|
18
16
|
|
19
17
|
## Usage
|
20
18
|
|
21
|
-
|
19
|
+
This gem has been extracted from [customink-webops/hostsfile code](https://github.com/customink-webops/hostsfile), to decouple it from Chef, so that it can be used independently.
|
22
20
|
|
23
21
|
## Contributing
|
24
22
|
|
@@ -26,4 +24,10 @@ TODO: Write usage instructions here
|
|
26
24
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
25
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
26
|
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
-
5. Create a new Pull Request
|
27
|
+
5. Create a new Pull Request
|
28
|
+
|
29
|
+
## Authors
|
30
|
+
|
31
|
+
- Tnarik Innael (@tnarik) : gem packaging, tests
|
32
|
+
- Seth Vargo (@sethvargo) : original code as part of [the 'hostsfile' cookbook](https://github.com/customink-webops/hostsfile)
|
33
|
+
- CustomInk, LCC : original code as part of [the 'hostsfile' cookbook](https://github.com/customink-webops/hostsfile)
|
data/hostsfile.gemspec
CHANGED
@@ -6,11 +6,11 @@ require 'hostsfile/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "hostsfile"
|
8
8
|
spec.version = Hostsfile::VERSION
|
9
|
-
spec.authors = ["
|
9
|
+
spec.authors = ["Tnarik Innael"]
|
10
10
|
spec.email = ["tnarik@lecafeautomatique.co.uk"]
|
11
11
|
spec.summary = %q{code from the hostsfile cookbook to allow reusability}
|
12
12
|
spec.description = %q{code from the hostsfile cookbook to allow reusability}
|
13
|
-
spec.homepage = ""
|
13
|
+
spec.homepage = "https://github.com/tnarik/hostsfile"
|
14
14
|
spec.license = "Apache-2.0"
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
@@ -18,14 +18,22 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
# development dependencies
|
21
22
|
spec.add_development_dependency "bundler", "~> 1.6"
|
22
23
|
spec.add_development_dependency "rake"
|
23
24
|
|
25
|
+
# development dependencies (testing)
|
24
26
|
spec.add_development_dependency "rspec", "~> 3.0"
|
25
27
|
spec.add_development_dependency "guard"
|
26
28
|
spec.add_development_dependency "guard-rspec"
|
29
|
+
spec.add_development_dependency "fakefs"
|
30
|
+
|
31
|
+
# development dependencies (coverage)
|
32
|
+
spec.add_development_dependency 'coveralls'
|
33
|
+
spec.add_development_dependency 'simplecov'
|
34
|
+
spec.add_development_dependency 'simplecov-console'
|
27
35
|
|
36
|
+
# development dependencies (notifier), controlled by .guard.rb
|
28
37
|
spec.add_development_dependency "terminal-notifier-guard"
|
29
|
-
# Pre OS X 10.8
|
30
|
-
# spec.add_development_dependency "ruby_gntp"
|
38
|
+
spec.add_development_dependency "ruby_gntp" # Pre OS X 10.8
|
31
39
|
end
|
data/lib/hostsfile/entry.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# Copyright 2013-14, Tnarik Innael
|
2
|
+
#
|
3
|
+
# Heavily based on:
|
1
4
|
# Copyright 2012-2013, Seth Vargo (from customink-webops/hostsfile/libraries/entry.rb)
|
2
5
|
# Copyright 2012, CustomInk, LCC
|
3
6
|
#
|
@@ -28,6 +31,14 @@ class Entry
|
|
28
31
|
# Return nil if the line is empty
|
29
32
|
return nil if entries.nil? || entries.empty?
|
30
33
|
|
34
|
+
# If the hostsfile has broken content:
|
35
|
+
#if entries[0].nil?
|
36
|
+
# raise ArgumentError, "Hostsfile has a line without IP address: #{line}"
|
37
|
+
#end
|
38
|
+
if entries[1].nil?
|
39
|
+
raise ArgumentError, "Hostsfile has a line without hostname: #{line}"
|
40
|
+
end
|
41
|
+
|
31
42
|
return self.new(
|
32
43
|
ip_address: entries[0],
|
33
44
|
hostname: entries[1],
|
@@ -38,31 +49,31 @@ class Entry
|
|
38
49
|
end
|
39
50
|
|
40
51
|
private
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
52
|
+
def extract_comment(line)
|
53
|
+
return nil if presence(line).nil?
|
54
|
+
line.split('#', 2).collect { |part| presence(part) }
|
55
|
+
end
|
45
56
|
|
46
|
-
|
47
|
-
|
57
|
+
def extract_priority(comment)
|
58
|
+
return nil if comment.nil?
|
48
59
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
60
|
+
if comment.include?('@')
|
61
|
+
comment.split('@', 2).collect { |part| presence(part) }
|
62
|
+
else
|
63
|
+
[comment, nil]
|
54
64
|
end
|
65
|
+
end
|
55
66
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
67
|
+
def extract_entries(entry)
|
68
|
+
return nil if entry.nil?
|
69
|
+
entry.split(/\s+/).collect { |entry| presence(entry) }.compact
|
70
|
+
end
|
60
71
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
72
|
+
def presence(string)
|
73
|
+
return nil if string.nil?
|
74
|
+
return nil if string.strip.empty?
|
75
|
+
string.strip
|
76
|
+
end
|
66
77
|
end
|
67
78
|
|
68
79
|
# @return [String]
|
@@ -90,7 +101,7 @@ class Entry
|
|
90
101
|
raise ArgumentError, ':ip_address and :hostname are both required options'
|
91
102
|
end
|
92
103
|
|
93
|
-
@ip_address = IPAddr.new(options[:ip_address]
|
104
|
+
@ip_address = IPAddr.new(remove_ip_scope(options[:ip_address]))
|
94
105
|
@hostname = options[:hostname]
|
95
106
|
@aliases = [options[:aliases]].flatten.compact
|
96
107
|
@comment = options[:comment]
|
@@ -121,32 +132,6 @@ class Entry
|
|
121
132
|
[ip_address, hosts, comments].compact.join("\t").strip
|
122
133
|
end
|
123
134
|
|
124
|
-
# The string representation of this Entry
|
125
|
-
#
|
126
|
-
# @return [String]
|
127
|
-
# the string representation of this entry
|
128
|
-
def to_s
|
129
|
-
"#<#{self.class.to_s} " + [
|
130
|
-
"ip_address: '#{ip_address}'",
|
131
|
-
"hostname: '#{hostname}'",
|
132
|
-
].join(', ') + '>'
|
133
|
-
end
|
134
|
-
|
135
|
-
# The object representation of this Entry
|
136
|
-
#
|
137
|
-
# @return [String]
|
138
|
-
# the object representation of this entry
|
139
|
-
def inspect
|
140
|
-
"#<#{self.class.to_s} " + [
|
141
|
-
"ip_address: '#{ip_address}'",
|
142
|
-
"hostname: '#{hostname}'",
|
143
|
-
"aliases: #{aliases.inspect}",
|
144
|
-
"comment: '#{comment}'",
|
145
|
-
"priority: #{priority}",
|
146
|
-
"calculated_priority?: #{@calculated_priority}",
|
147
|
-
].join(', ') + '>'
|
148
|
-
end
|
149
|
-
|
150
135
|
# Returns true if priority is calculated
|
151
136
|
#
|
152
137
|
# @return [Boolean]
|
@@ -156,20 +141,32 @@ class Entry
|
|
156
141
|
end
|
157
142
|
|
158
143
|
private
|
144
|
+
# Calculates the relative priority of this entry.
|
145
|
+
#
|
146
|
+
# @return [Fixnum]
|
147
|
+
# the relative priority of this item
|
148
|
+
def calculated_priority
|
149
|
+
@calculated_priority = true
|
159
150
|
|
160
|
-
|
161
|
-
#
|
162
|
-
#
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
151
|
+
priority ||= 81 if ip_address == IPAddr.new('127.0.0.1')
|
152
|
+
priority ||= 80 if IPAddr.new('127.0.0.0/8').include?(ip_address) # local
|
153
|
+
priority ||= 60 if ip_address.ipv4? # ipv4
|
154
|
+
priority ||= 20 if ip_address.ipv6? # ipv6
|
155
|
+
|
156
|
+
return priority || 00
|
157
|
+
end
|
158
|
+
|
159
|
+
# Removes the scopes pieces of the address, because of the below reasons.
|
160
|
+
#
|
161
|
+
# @see https://bugs.ruby-lang.org/issues/8464
|
162
|
+
# @see https://github.com/customink-webops/hostsfile/issues/51
|
163
|
+
#
|
164
|
+
# @return [String, nil]
|
165
|
+
#
|
166
|
+
def remove_ip_scope(address)
|
167
|
+
return nil if address.nil?
|
168
|
+
address.to_s.sub(/%.*/, '')
|
169
|
+
end
|
173
170
|
end
|
174
171
|
|
175
172
|
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
# Copyright 2013-14, Tnarik Innael
|
2
|
+
#
|
3
|
+
# Heavily based on:
|
2
4
|
# Copyright 2012-2013, Seth Vargo (from customink-webops/hostsfile/libraries/manipulator.rb)
|
3
5
|
# Copyright 2012, CustomInk, LCC
|
4
6
|
#
|
@@ -9,18 +11,21 @@ module Hostsfile
|
|
9
11
|
|
10
12
|
class Manipulator
|
11
13
|
attr_reader :entries
|
12
|
-
|
13
14
|
# Create a new Manipulator object (aka an /etc/hosts manipulator). If a
|
14
|
-
# hostsfile is not found, a Exception is risen
|
15
|
-
#
|
15
|
+
# hostsfile is not found, a Exception is risen.
|
16
|
+
# Parameters are optional (see #hostsfile_path)
|
16
17
|
#
|
17
|
-
# @param [
|
18
|
-
# the
|
18
|
+
# @param [String] path
|
19
|
+
# the file path for the host file
|
20
|
+
# @param [String] family
|
21
|
+
# the OS family ('windows' or anything else for POSIX support)
|
22
|
+
# @param [String] system_directory
|
23
|
+
# System directory for the 'windows' family (like C:\\Windows\\system32)
|
19
24
|
# @return [Manipulator]
|
20
|
-
# a class designed to manipulate the
|
25
|
+
# a class designed to manipulate the /etc/hosts file
|
21
26
|
def initialize(path = nil, family = nil, system_directory = nil)
|
22
27
|
# Fail if no hostsfile is found
|
23
|
-
unless ::File.exists?(hostsfile_path)
|
28
|
+
unless ::File.exists?(hostsfile_path(path, family, system_directory))
|
24
29
|
raise "No hostsfile exists at '#{hostsfile_path}'!"
|
25
30
|
end
|
26
31
|
|
@@ -113,28 +118,19 @@ class Manipulator
|
|
113
118
|
# Save the new hostsfile to the target machine. This method will only write the
|
114
119
|
# hostsfile if the current version has changed. In other words, it is convergent.
|
115
120
|
def save
|
116
|
-
entries = []
|
117
|
-
entries << '#'
|
118
|
-
entries << '# This file is managed by Chef, using the hostsfile cookbook.'
|
119
|
-
entries << '# Editing this file by hand is highly discouraged!'
|
120
|
-
entries << '#'
|
121
|
-
entries << '# Comments containing an @ sign should not be modified or else'
|
122
|
-
entries << '# hostsfile will be unable to guarantee relative priority in'
|
123
|
-
entries << '# future Chef runs!'
|
124
|
-
entries << '#'
|
125
|
-
entries << ''
|
126
|
-
entries += unique_entries.map(&:to_line)
|
127
|
-
entries << ''
|
128
|
-
|
129
|
-
contents = entries.join("\n")
|
130
|
-
contents_sha = Digest::SHA512.hexdigest(contents)
|
131
|
-
|
132
121
|
# Only write out the file if the contents have changed...
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
122
|
+
::File.open(hostsfile_path, 'w') do |f|
|
123
|
+
f.write(new_content)
|
124
|
+
end if content_changed?
|
125
|
+
end
|
126
|
+
|
127
|
+
# Determine if the content of the hostfile has changed by comparing sha
|
128
|
+
# values of existing file and new content
|
129
|
+
#
|
130
|
+
# @return [Boolean]
|
131
|
+
def content_changed?
|
132
|
+
new_sha = Digest::SHA512.hexdigest(new_content)
|
133
|
+
new_sha != current_sha
|
138
134
|
end
|
139
135
|
|
140
136
|
# Find an entry by the given IP Address.
|
@@ -152,108 +148,144 @@ class Manipulator
|
|
152
148
|
# Determine if the current hostsfile contains the given resource. This
|
153
149
|
# is really just a proxy to {find_resource_by_ip_address} /
|
154
150
|
#
|
155
|
-
# @param [
|
156
|
-
#
|
151
|
+
# @param [String] ip_address
|
152
|
+
# the IP Address of the entry to check
|
157
153
|
# @return [Boolean]
|
158
|
-
def contains?(
|
159
|
-
!!find_entry_by_ip_address(
|
154
|
+
def contains?(ip_address)
|
155
|
+
!!find_entry_by_ip_address(ip_address)
|
160
156
|
end
|
161
157
|
|
162
158
|
private
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
159
|
+
# The path to the current hostsfile.
|
160
|
+
# If not path is provided, a default is guessed based on 'family' and 'system_directory'
|
161
|
+
# If path is provided, it takes priority
|
162
|
+
#
|
163
|
+
# @param [String] path
|
164
|
+
# the file path for the host file
|
165
|
+
# @param [String] family
|
166
|
+
# the OS family ('windows' or anything else for POSIX support)
|
167
|
+
# @param [String] system_directory
|
168
|
+
# System directory for the 'windows' family (default C:\\Windows\\system32)
|
169
|
+
# @return [String]
|
170
|
+
# the full path to the hostsfile, depending on the operating system
|
171
|
+
def hostsfile_path (path = nil, family = nil, system_directory = nil)
|
172
|
+
return @hostsfile_path if @hostsfile_path
|
173
|
+
@hostsfile_path = path || case family
|
174
|
+
when 'windows'
|
175
|
+
system_directory ||= File.join('C:','Windows','system32')
|
176
|
+
File.join("#{system_directory}", 'drivers', 'etc', 'hosts')
|
177
|
+
else
|
178
|
+
'/etc/hosts'
|
179
|
+
end
|
180
|
+
end
|
177
181
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
182
|
+
# The header of the new hostsfile
|
183
|
+
#
|
184
|
+
# @return [Array]
|
185
|
+
# an array of header comments
|
186
|
+
def hostsfile_header
|
187
|
+
lines = []
|
188
|
+
lines << '#'
|
189
|
+
lines << '# This file is managed by the hostsfile gem.'
|
190
|
+
lines << '# Editing this file by hand is highly discouraged!'
|
191
|
+
lines << '#'
|
192
|
+
lines << '# Comments containing an @ sign should not be modified or else'
|
193
|
+
lines << '# hostsfile will be unable to guarantee relative priority in'
|
194
|
+
lines << '# future runs!'
|
195
|
+
lines << '#'
|
196
|
+
lines << ''
|
197
|
+
end
|
185
198
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
199
|
+
# The content that will be written to the hostfile
|
200
|
+
#
|
201
|
+
# @return [String]
|
202
|
+
# the full contents of the hostfile to be written
|
203
|
+
def new_content
|
204
|
+
lines = hostsfile_header
|
205
|
+
lines += unique_entries.map(&:to_line)
|
206
|
+
lines << ''
|
207
|
+
lines.join("\n")
|
208
|
+
end
|
196
209
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
210
|
+
# The current sha of the system hostsfile.
|
211
|
+
#
|
212
|
+
# @return [String]
|
213
|
+
# the sha of the current hostsfile
|
214
|
+
def current_sha
|
215
|
+
@current_sha ||= Digest::SHA512.hexdigest(File.read(hostsfile_path))
|
216
|
+
end
|
217
|
+
|
218
|
+
# Normalize the given list of elements into a single array with no nil
|
219
|
+
# values and no duplicate values.
|
220
|
+
#
|
221
|
+
# @param [Object] things
|
222
|
+
#
|
223
|
+
# @return [Array]
|
224
|
+
# a normalized array of things
|
225
|
+
def normalize(*things)
|
226
|
+
things.flatten.compact.uniq
|
227
|
+
end
|
205
228
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
entry = ::Hostsfile::Entry.parse(line)
|
215
|
-
next if entry.nil?
|
229
|
+
# This is a crazy way of ensuring unique objects in an array using a Hash.
|
230
|
+
#
|
231
|
+
# @return [Array]
|
232
|
+
# the sorted list of entires that are unique
|
233
|
+
def unique_entries
|
234
|
+
entries = Hash[*@entries.map { |entry| [entry.ip_address, entry] }.flatten].values
|
235
|
+
entries.sort_by { |e| [-e.priority.to_i, e.hostname.to_s] }
|
236
|
+
end
|
216
237
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
238
|
+
# Takes /etc/hosts file contents and builds a flattened entries
|
239
|
+
# array so that each IP address has only one line and multiple hostnames
|
240
|
+
# are flattened into a list of aliases.
|
241
|
+
#
|
242
|
+
# @param [Array] contents
|
243
|
+
# Array of lines from /etc/hosts file
|
244
|
+
def collect_and_flatten(contents)
|
245
|
+
contents.each do |line|
|
246
|
+
entry = ::Hostsfile::Entry.parse(line)
|
247
|
+
next if entry.nil?
|
248
|
+
|
249
|
+
append(
|
250
|
+
ip_address: entry.ip_address,
|
251
|
+
hostname: entry.hostname,
|
252
|
+
aliases: entry.aliases,
|
253
|
+
comment: entry.comment,
|
254
|
+
priority: !entry.calculated_priority? && entry.priority,
|
255
|
+
)
|
225
256
|
end
|
257
|
+
end
|
226
258
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
259
|
+
# Removes duplicate hostnames in other files ensuring they are unique
|
260
|
+
#
|
261
|
+
# @param [Entry] entry
|
262
|
+
# the entry to keep the hostname and aliases from
|
263
|
+
#
|
264
|
+
# @return [nil]
|
265
|
+
def remove_existing_hostnames(entry)
|
266
|
+
@entries.delete(entry)
|
267
|
+
changed_hostnames = [entry.hostname, entry.aliases].flatten.uniq
|
236
268
|
|
237
|
-
|
238
|
-
|
239
|
-
|
269
|
+
@entries = @entries.collect do |entry|
|
270
|
+
entry.hostname = nil if changed_hostnames.include?(entry.hostname)
|
271
|
+
entry.aliases = entry.aliases - changed_hostnames
|
240
272
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
else
|
245
|
-
entry.hostname = entry.aliases.shift
|
246
|
-
entry
|
247
|
-
end
|
273
|
+
if entry.hostname.nil?
|
274
|
+
if entry.aliases.empty?
|
275
|
+
nil
|
248
276
|
else
|
277
|
+
entry.hostname = entry.aliases.shift
|
249
278
|
entry
|
250
279
|
end
|
251
|
-
|
280
|
+
else
|
281
|
+
entry
|
282
|
+
end
|
283
|
+
end.compact
|
252
284
|
|
253
|
-
|
285
|
+
@entries << entry
|
254
286
|
|
255
|
-
|
256
|
-
|
287
|
+
nil
|
288
|
+
end
|
257
289
|
end
|
258
290
|
|
259
291
|
end
|
data/lib/hostsfile/version.rb
CHANGED
data/spec/entry_spec.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'fakefs/spec_helpers'
|
2
|
+
|
3
|
+
describe Hostsfile do
|
4
|
+
describe "::Entry" do
|
5
|
+
let(:entry) { Hostsfile::Entry.new ip_address: "0.0.0.0", hostname: "test" }
|
6
|
+
|
7
|
+
context "#parse" do
|
8
|
+
it "raises a fatal error if the hostname is missing (considers the first field the IP" do
|
9
|
+
expect { Hostsfile::Entry.parse "0.0.0.0"}.to raise_error(ArgumentError, /Hostsfile has a line without hostname/)
|
10
|
+
expect { Hostsfile::Entry.parse " hostname"}.to raise_error(ArgumentError, /Hostsfile has a line without hostname/)
|
11
|
+
expect { Hostsfile::Entry.parse " \thostname"}.to raise_error(ArgumentError, /Hostsfile has a line without hostname/)
|
12
|
+
expect { Hostsfile::Entry.parse "\thostname"}.to raise_error(ArgumentError, /Hostsfile has a line without hostname/)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "#to_line" do
|
17
|
+
it "exists" do
|
18
|
+
expect(entry.respond_to? :to_line).to eq(true)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "generates a proper line" do
|
22
|
+
expect(entry.to_line).to eq("0.0.0.0\ttest")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
## Entry
|
29
|
+
# def parse(line)
|
30
|
+
# private
|
31
|
+
# def extract_comment(line)
|
32
|
+
# def extract_priority(comment)
|
33
|
+
# def extract_entries(entry)
|
34
|
+
# def presence(string)
|
35
|
+
#
|
36
|
+
# def initialize(options = {})
|
37
|
+
# def priority=(new_priority)
|
38
|
+
# def to_line
|
39
|
+
# def to_s
|
40
|
+
# def inspect
|
41
|
+
# def calculated_priority?
|
42
|
+
# private
|
43
|
+
# def calculated_priority
|
@@ -0,0 +1,10 @@
|
|
1
|
+
##
|
2
|
+
# Host Database
|
3
|
+
#
|
4
|
+
# localhost is used to configure the loopback interface
|
5
|
+
# when the system is booting. Do not change this entry.
|
6
|
+
##
|
7
|
+
127.0.0.1 localhost
|
8
|
+
192.0.2.1 awesome.example.com
|
9
|
+
192.0.2.2 fine.example.com refined.example.com
|
10
|
+
2001:db8::1 ipv6.example.com
|
data/spec/hostsfile_spec.rb
CHANGED
@@ -1,75 +1,9 @@
|
|
1
|
+
require 'fakefs/spec_helpers'
|
2
|
+
|
1
3
|
describe Hostsfile do
|
2
4
|
describe "::VERSION" do
|
3
|
-
it "
|
5
|
+
it "is defined" do
|
4
6
|
expect(Hostsfile::VERSION).not_to be_empty
|
5
7
|
end
|
6
8
|
end
|
7
|
-
|
8
|
-
describe "::Entry" do
|
9
|
-
it "exists" do
|
10
|
-
expect(Hostsfile::Entry::respond_to? :new).to eq(true)
|
11
|
-
end
|
12
|
-
|
13
|
-
it "#to_line" do
|
14
|
-
h = Hostsfile::Entry.new ip_address: "0.0.0.0", hostname: "test"
|
15
|
-
expect(h.respond_to? :to_line).to eq(true)
|
16
|
-
end
|
17
|
-
|
18
|
-
it "#to_line_response" do
|
19
|
-
h = Hostsfile::Entry.new ip_address: "0.0.0.0", hostname: "test"
|
20
|
-
expect(h.to_line).to eq("0.0.0.0\ttest")
|
21
|
-
end
|
22
|
-
|
23
|
-
it "#to_s" do
|
24
|
-
h = Hostsfile::Entry.new ip_address: "0.0.0.0", hostname: "test"
|
25
|
-
expect(h.respond_to? :to_line).to eq(true)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
describe "::Manipulator" do
|
30
|
-
it "exists" do
|
31
|
-
expect(Hostsfile::Manipulator::respond_to? :new).to eq(true)
|
32
|
-
end
|
33
|
-
|
34
|
-
#it "#append" do
|
35
|
-
# h = Hostsfile::Manipulator.new
|
36
|
-
# expect(h.respond_to? :append).to eq(true)
|
37
|
-
#end
|
38
|
-
end
|
39
9
|
end
|
40
|
-
|
41
|
-
## Manipulator
|
42
|
-
# def initialize(path = nil, family = nil, system_directory = nil)
|
43
|
-
# def ip_addresses
|
44
|
-
# def add(options = {})
|
45
|
-
# def update(options = {})
|
46
|
-
# def append(options = {})
|
47
|
-
# def remove(ip_address)
|
48
|
-
# def save
|
49
|
-
# def find_entry_by_ip_address(ip_address)
|
50
|
-
# def contains?(resource)
|
51
|
-
# private
|
52
|
-
# def hostsfile_path (path = nil, family = nil, system_directory = nil)
|
53
|
-
# def current_sha
|
54
|
-
# def normalize(*things)
|
55
|
-
# def unique_entries
|
56
|
-
# def collect_and_flatten(contents)
|
57
|
-
# def remove_existing_hostnames(entry)
|
58
|
-
|
59
|
-
|
60
|
-
## Entry
|
61
|
-
# def parse(line)
|
62
|
-
# private
|
63
|
-
# def extract_comment(line)
|
64
|
-
# def extract_priority(comment)
|
65
|
-
# def extract_entries(entry)
|
66
|
-
# def presence(string)
|
67
|
-
#
|
68
|
-
# def initialize(options = {})
|
69
|
-
# def priority=(new_priority)
|
70
|
-
# def to_line
|
71
|
-
# def to_s
|
72
|
-
# def inspect
|
73
|
-
# def calculated_priority?
|
74
|
-
# private
|
75
|
-
# def calculated_priority
|
@@ -0,0 +1,202 @@
|
|
1
|
+
require 'fakefs/spec_helpers'
|
2
|
+
|
3
|
+
describe Hostsfile do
|
4
|
+
describe "::Manipulator" do
|
5
|
+
include FakeFS::SpecHelpers
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
fixture_to_fakefs("sample_hosts", "/etc/hosts")
|
9
|
+
fixture_to_fakefs("sample_hosts", "/windows/drivers/etc/hosts")
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:manipulator) { Hostsfile::Manipulator.new }
|
13
|
+
|
14
|
+
context "#initialize" do
|
15
|
+
it "raises a fatal error if the hostfile does not exist" do
|
16
|
+
expect { Hostsfile::Manipulator.new "/etc/hosts_does_not_exist"}.to raise_error(RuntimeError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "reads the default /etc/hosts file if none is specified" do
|
20
|
+
expect { Hostsfile::Manipulator.new }.to_not raise_error
|
21
|
+
expect(Hostsfile::Manipulator.new.ip_addresses.size).to be(4)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "reads the default drivers/etc/hosts file for windows with a system directory" do
|
25
|
+
expect { Hostsfile::Manipulator.new nil, 'windows', '/windows'}.to_not raise_error
|
26
|
+
expect(Hostsfile::Manipulator.new(nil, 'windows', '/windows').ip_addresses.size).to be(4)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "#ip_addresses" do
|
31
|
+
it "reads ip addresses correctly" do
|
32
|
+
expect(manipulator.ip_addresses.size).to be(4)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context '#find_entry_by_ip_address' do
|
37
|
+
it 'finds the associated entry' do
|
38
|
+
expect( manipulator.find_entry_by_ip_address('127.0.0.1') ).not_to be_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'returns nil if the entry does not exist' do
|
42
|
+
expect( manipulator.find_entry_by_ip_address('192.0.2.0') ).to be_nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context '#contains?' do
|
47
|
+
it 'detects an existing entry' do
|
48
|
+
expect( manipulator.contains?('127.0.0.1') ).to be_truthy
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'detects the non existing entry' do
|
52
|
+
expect( manipulator.contains?('192.0.2.0') ).to be_falsey
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "#add" do
|
57
|
+
let(:options) { { ip_address: '192.0.2.0', hostname: 'example.com', aliases: nil, comment: 'Some comment', priority: 5 } }
|
58
|
+
let(:minimal_options) { { ip_address: '192.0.2.0', hostname: 'example.com' } }
|
59
|
+
|
60
|
+
it "requires at least IP Address and Hostname" do
|
61
|
+
expect { manipulator.add(ip_address: options[:ip_address]) }.to raise_error(ArgumentError)
|
62
|
+
expect { manipulator.add(hostname: options[:hostname]) }.to raise_error(ArgumentError)
|
63
|
+
expect { manipulator.add(aliases: options[:aliases]) }.to raise_error(ArgumentError)
|
64
|
+
expect { manipulator.add(comment: options[:comment]) }.to raise_error(ArgumentError)
|
65
|
+
|
66
|
+
expect { manipulator.add(options.reject {|k| k == :ip_address}) }.to raise_error(ArgumentError)
|
67
|
+
expect { manipulator.add(options.reject {|k| k == :hostname}) }.to raise_error(ArgumentError)
|
68
|
+
|
69
|
+
expect { manipulator.add(minimal_options) }.to change{manipulator.ip_addresses.size}.from(4).to(5)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "add entries in memory until save" do # Includes debugging
|
73
|
+
expect { manipulator.add(options) }.to change{manipulator.ip_addresses.size}.from(4).to(5)
|
74
|
+
refreshed_manipulator = Hostsfile::Manipulator.new
|
75
|
+
expect(refreshed_manipulator.ip_addresses.size).to be(4)
|
76
|
+
manipulator.save
|
77
|
+
refreshed_manipulator = Hostsfile::Manipulator.new
|
78
|
+
expect(refreshed_manipulator.ip_addresses.size).to be(5)
|
79
|
+
puts File.read("/etc/hosts")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "#update" do
|
84
|
+
let(:options) { { ip_address: '127.0.0.1', hostname: 'new.example.com' } }
|
85
|
+
let(:not_existing_options) { { ip_address: '192.0.2.0', hostname: 'new.example.com' } }
|
86
|
+
context "when the entry exists" do
|
87
|
+
it "does not add a new entry" do
|
88
|
+
expect { manipulator.update(options) }.not_to change{manipulator.ip_addresses.size}
|
89
|
+
end
|
90
|
+
|
91
|
+
it "updates the entry" do # Includes debugging
|
92
|
+
p manipulator.find_entry_by_ip_address(options[:ip_address])
|
93
|
+
expect { manipulator.update(options) }.to change{manipulator.find_entry_by_ip_address(options[:ip_address]).hostname}
|
94
|
+
expect( manipulator.find_entry_by_ip_address(options[:ip_address]).hostname ).to eq(options[:hostname])
|
95
|
+
p manipulator.find_entry_by_ip_address(options[:ip_address])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
context "when the entry does not exist" do
|
99
|
+
it "does nothing" do
|
100
|
+
expect { manipulator.update(not_existing_options) }.not_to change{manipulator.ip_addresses.size}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "#append" do
|
106
|
+
let(:options) { { ip_address: '127.0.0.1', hostname: 'new.example.com', aliases: "alias.example.com", comment: 'Some comment', priority: 5 } }
|
107
|
+
let(:not_existing_options) { { ip_address: '192.0.2.0', hostname: 'example.com', aliases: nil, comment: 'Some comment', priority: 5 } }
|
108
|
+
let(:unique_options) { { ip_address: '192.0.2.3', hostname: 'awesome.example.com', aliases: nil, comment: 'Was previously the single hostname of 192.0.2.1', unique: true } }
|
109
|
+
let(:unique_options_fine) { { ip_address: '192.0.2.3', hostname: 'fine.example.com', aliases: nil, comment: 'Was previously the hostname of 192.0.2.2', unique: true } }
|
110
|
+
let(:unique_options_refined) { { ip_address: '192.0.2.3', hostname: 'refined.example.com', aliases: nil, comment: 'Was previously an alias of 192.0.2.2', unique: true } }
|
111
|
+
|
112
|
+
context "when the entry exists by IP" do
|
113
|
+
it "does not update hostname, instead adds the new one as an alias" do
|
114
|
+
original_entry = manipulator.find_entry_by_ip_address(options[:ip_address])
|
115
|
+
expect { manipulator.append(options) }.not_to change{manipulator.ip_addresses.size}
|
116
|
+
expect( manipulator.find_entry_by_ip_address(options[:ip_address]).hostname ).to eq(original_entry.hostname)
|
117
|
+
expect( manipulator.find_entry_by_ip_address(options[:ip_address]).aliases ).to include("new.example.com")
|
118
|
+
end
|
119
|
+
it "updates aliases" do
|
120
|
+
expect { manipulator.append(options) }.not_to change{manipulator.ip_addresses.size}
|
121
|
+
expect( manipulator.find_entry_by_ip_address(options[:ip_address]).aliases ).to include("alias.example.com")
|
122
|
+
end
|
123
|
+
it "updates comment" do
|
124
|
+
expect { manipulator.append(options) }.not_to change{manipulator.ip_addresses.size}
|
125
|
+
expect( manipulator.find_entry_by_ip_address(options[:ip_address]).comment ).to eq(options[:comment])
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "when the entry exists by hostname" do
|
130
|
+
context "when tagged as unique" do
|
131
|
+
it "replaces it when original entry contains only name" do
|
132
|
+
original_entry = manipulator.find_entry_by_ip_address('192.0.2.1').dup
|
133
|
+
expect( original_entry.hostname ).to eq(unique_options[:hostname])
|
134
|
+
expect { manipulator.append(unique_options) }.not_to change{manipulator.ip_addresses.size}
|
135
|
+
expect( manipulator.find_entry_by_ip_address(unique_options[:ip_address]).hostname ).to eq(unique_options[:hostname])
|
136
|
+
expect( manipulator.find_entry_by_ip_address(original_entry.ip_address) ).to be_nil
|
137
|
+
end
|
138
|
+
|
139
|
+
it "adds it when original entry has alises (and hostnames match)" do
|
140
|
+
original_entry = manipulator.find_entry_by_ip_address('192.0.2.2').dup
|
141
|
+
expect( original_entry.hostname ).to eq(unique_options_fine[:hostname])
|
142
|
+
|
143
|
+
expect { manipulator.append(unique_options_fine) }.to change{manipulator.ip_addresses.size}.from(4).to(5)
|
144
|
+
expect( manipulator.find_entry_by_ip_address(unique_options_fine[:ip_address]).hostname ).to eq(unique_options_fine[:hostname])
|
145
|
+
refreshed_original_entry = manipulator.find_entry_by_ip_address(original_entry.ip_address)
|
146
|
+
expect( refreshed_original_entry ).not_to be_nil
|
147
|
+
expect( original_entry.aliases ).to include(refreshed_original_entry.hostname )
|
148
|
+
expect( original_entry.aliases.size - refreshed_original_entry.aliases.size ).to eq(1)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "adds it when original entry has aliases (one being the hostname)" do
|
152
|
+
original_entry = manipulator.find_entry_by_ip_address('192.0.2.2').dup
|
153
|
+
expect( original_entry.aliases ).to include(unique_options_refined[:hostname])
|
154
|
+
|
155
|
+
expect { manipulator.append(unique_options_refined) }.to change{manipulator.ip_addresses.size}.from(4).to(5)
|
156
|
+
expect( manipulator.find_entry_by_ip_address(unique_options_refined[:ip_address]).hostname ).to eq(unique_options_refined[:hostname])
|
157
|
+
expect( manipulator.find_entry_by_ip_address(original_entry.ip_address) ).not_to be_nil
|
158
|
+
expect( manipulator.find_entry_by_ip_address(original_entry.ip_address).aliases ).not_to include(unique_options_refined[:hostname])
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
it "adds a new entry if not unique" do
|
164
|
+
original_entry = manipulator.find_entry_by_ip_address('192.0.2.1').dup
|
165
|
+
expect( original_entry.hostname ).to eq(unique_options[:hostname])
|
166
|
+
expect { manipulator.append(unique_options.reject {|k| k == :unique}) }.to change{manipulator.ip_addresses.size}.from(4).to(5)
|
167
|
+
expect( manipulator.find_entry_by_ip_address(unique_options[:ip_address]).hostname ).to eq(unique_options[:hostname])
|
168
|
+
expect( manipulator.find_entry_by_ip_address(original_entry.ip_address) ).not_to be_nil
|
169
|
+
end
|
170
|
+
end
|
171
|
+
context "when the entry does not exist" do
|
172
|
+
it "adds the new entry" do
|
173
|
+
expect { manipulator.append(not_existing_options) }.to change{manipulator.ip_addresses.size}.from(4).to(5)
|
174
|
+
expect( manipulator.find_entry_by_ip_address(not_existing_options[:ip_address]).hostname ).to eq('example.com')
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
context "#remove" do
|
180
|
+
context "when the entry exists" do
|
181
|
+
it "is removed" do
|
182
|
+
expect { manipulator.remove('127.0.0.1') }.to change{manipulator.ip_addresses.size}.from(4).to(3)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
context "when the entry does not exist" do
|
186
|
+
it "does nothing" do
|
187
|
+
expect { manipulator.remove('192.0.2.0') }.not_to change{manipulator.ip_addresses.size}
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Contains will probably dissapear, as it links to entries specifically
|
193
|
+
|
194
|
+
it "ip addresses are removed works on memory" do
|
195
|
+
expect(manipulator.ip_addresses.size).to be(4)
|
196
|
+
manipulator.add(ip_address: '192.0.2.0', hostname: 'test')
|
197
|
+
expect(manipulator.ip_addresses.size).to be(5)
|
198
|
+
expect { manipulator.remove('192.0.2.0') }.to change{manipulator.ip_addresses.size}.from(5).to(4)
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,4 +1,38 @@
|
|
1
1
|
require 'bundler/setup'
|
2
2
|
Bundler.setup
|
3
3
|
|
4
|
+
require 'simplecov'
|
5
|
+
require 'simplecov-console'
|
6
|
+
require 'coveralls'
|
7
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
8
|
+
SimpleCov::Formatter::HTMLFormatter,
|
9
|
+
SimpleCov::Formatter::Console,
|
10
|
+
Coveralls::SimpleCov::Formatter
|
11
|
+
]
|
12
|
+
|
13
|
+
SimpleCov.start do
|
14
|
+
add_filter "/spec/"
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def fixture_path
|
19
|
+
File.expand_path("../fixtures", __FILE__)
|
20
|
+
end
|
21
|
+
|
22
|
+
def fixture(*segments)
|
23
|
+
fakefs_status = (defined? FakeFS).nil? ? false : FakeFS.activated?
|
24
|
+
FakeFS.deactivate! if fakefs_status
|
25
|
+
fixture = File.read(File.join(fixture_path, *segments))
|
26
|
+
FakeFS.activate! if fakefs_status
|
27
|
+
fixture
|
28
|
+
end
|
29
|
+
|
30
|
+
def fixture_to_fakefs(name, filepath)
|
31
|
+
raise "FakeFS required but not installed or activated" unless !(defined? FakeFS).nil? && FakeFS.activated?
|
32
|
+
|
33
|
+
fixture_content = fixture(name)
|
34
|
+
FileUtils.mkdir_p(File.dirname(filepath))
|
35
|
+
File.open(filepath, "w") { |f| f.write(fixture_content) }
|
36
|
+
end
|
37
|
+
|
4
38
|
require 'hostsfile'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hostsfile
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Tnarik Innael
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-08-
|
11
|
+
date: 2014-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -80,6 +80,62 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: fakefs
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: coveralls
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: simplecov-console
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
83
139
|
- !ruby/object:Gem::Dependency
|
84
140
|
name: terminal-notifier-guard
|
85
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +150,20 @@ dependencies:
|
|
94
150
|
- - ">="
|
95
151
|
- !ruby/object:Gem::Version
|
96
152
|
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: ruby_gntp
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
97
167
|
description: code from the hostsfile cookbook to allow reusability
|
98
168
|
email:
|
99
169
|
- tnarik@lecafeautomatique.co.uk
|
@@ -114,9 +184,12 @@ files:
|
|
114
184
|
- lib/hostsfile/entry.rb
|
115
185
|
- lib/hostsfile/manipulator.rb
|
116
186
|
- lib/hostsfile/version.rb
|
187
|
+
- spec/entry_spec.rb
|
188
|
+
- spec/fixtures/sample_hosts
|
117
189
|
- spec/hostsfile_spec.rb
|
190
|
+
- spec/manipulator_spec.rb
|
118
191
|
- spec/spec_helper.rb
|
119
|
-
homepage:
|
192
|
+
homepage: https://github.com/tnarik/hostsfile
|
120
193
|
licenses:
|
121
194
|
- Apache-2.0
|
122
195
|
metadata: {}
|
@@ -141,5 +214,8 @@ signing_key:
|
|
141
214
|
specification_version: 4
|
142
215
|
summary: code from the hostsfile cookbook to allow reusability
|
143
216
|
test_files:
|
217
|
+
- spec/entry_spec.rb
|
218
|
+
- spec/fixtures/sample_hosts
|
144
219
|
- spec/hostsfile_spec.rb
|
220
|
+
- spec/manipulator_spec.rb
|
145
221
|
- spec/spec_helper.rb
|