hostsfile 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b51455b82fe75732e2b22f6a9222a51abb16b1fa
4
- data.tar.gz: 183d81cc05822093b1d3094f3f523da5a98b7a2e
3
+ metadata.gz: 0df9590599c111715cca1d3e873ccf494a58b6ba
4
+ data.tar.gz: ce025bcf57210c92e589356129cc775dbdcb55dd
5
5
  SHA512:
6
- metadata.gz: 58ff7a872890643f04307497a6de91f8929890b67fbd3dca1c3d6f828cce7453b0e73c7f430fdd107af29aeb65269d4a079561f7f3046940901d0d4c4d3c1592
7
- data.tar.gz: f307fe72a5555b58e10dc8c77fc1f2e77b68387763a165401830d13b3b653d35856a5d712f7a51110ee4e3ee137029a316fb1653956ed9eef3f05604f06cb4d8
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/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
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
- TODO: Write a gem description
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
- Add this line to your application's Gemfile:
11
+ Install via Rubygems or Gemfile
8
12
 
9
- gem 'hostsfile'
10
-
11
- And then execute:
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
- TODO: Write usage instructions here
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 = ["tnarik"]
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
@@ -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
- def extract_comment(line)
42
- return nil if presence(line).nil?
43
- line.split('#', 2).collect { |part| presence(part) }
44
- end
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
- def extract_priority(comment)
47
- return nil if comment.nil?
57
+ def extract_priority(comment)
58
+ return nil if comment.nil?
48
59
 
49
- if comment.include?('@')
50
- comment.split('@', 2).collect { |part| presence(part) }
51
- else
52
- [comment, nil]
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
- def extract_entries(entry)
57
- return nil if entry.nil?
58
- entry.split(/\s+/).collect { |entry| presence(entry) }.compact
59
- end
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
- def presence(string)
62
- return nil if string.nil?
63
- return nil if string.strip.empty?
64
- string.strip
65
- end
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].to_s)
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
- # Calculates the relative priority of this entry.
161
- #
162
- # @return [Fixnum]
163
- # the relative priority of this item
164
- def calculated_priority
165
- @calculated_priority = true
166
-
167
- return 81 if ip_address == IPAddr.new('127.0.0.1')
168
- return 80 if IPAddr.new('127.0.0.0/8').include?(ip_address) # local
169
- return 60 if ip_address.ipv4? # ipv4
170
- return 20 if ip_address.ipv6? # ipv6
171
- return 00
172
- end
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, causing
15
- # the process to terminate on the node and the converge will fail.
15
+ # hostsfile is not found, a Exception is risen.
16
+ # Parameters are optional (see #hostsfile_path)
16
17
  #
17
- # @param [Chef::node] node
18
- # the current Chef node
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 node's /etc/hosts file
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
- if contents_sha != current_sha
134
- ::File.open(hostsfile_path, 'w') do |f|
135
- f.write(contents)
136
- end
137
- end
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 [Chef::Resource] resource
156
- #
151
+ # @param [String] ip_address
152
+ # the IP Address of the entry to check
157
153
  # @return [Boolean]
158
- def contains?(resource)
159
- !!find_entry_by_ip_address(resource.ip_address)
154
+ def contains?(ip_address)
155
+ !!find_entry_by_ip_address(ip_address)
160
156
  end
161
157
 
162
158
  private
163
- # The path to the current hostsfile.
164
- #
165
- # @return [String]
166
- # the full path to the hostsfile, depending on the operating system
167
- # can also be overriden in the attributes
168
- def hostsfile_path (path = nil, family = nil, system_directory = nil)
169
- return @hostsfile_path if @hostsfile_path
170
- @hostsfile_path = path || case family
171
- when 'windows'
172
- "#{system_directory}\\drivers\\etc\\hosts"
173
- else
174
- '/etc/hosts'
175
- end
176
- end
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
- # The current sha of the system hostsfile.
179
- #
180
- # @return [String]
181
- # the sha of the current hostsfile
182
- def current_sha
183
- @current_sha ||= Digest::SHA512.hexdigest(File.read(hostsfile_path))
184
- end
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
- # Normalize the given list of elements into a single array with no nil
187
- # values and no duplicate values.
188
- #
189
- # @param [Object] things
190
- #
191
- # @return [Array]
192
- # a normalized array of things
193
- def normalize(*things)
194
- things.flatten.compact.uniq
195
- end
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
- # This is a crazy way of ensuring unique objects in an array using a Hash.
198
- #
199
- # @return [Array]
200
- # the sorted list of entires that are unique
201
- def unique_entries
202
- entries = Hash[*@entries.map { |entry| [entry.ip_address, entry] }.flatten].values
203
- entries.sort_by { |e| [-e.priority.to_i, e.hostname.to_s] }
204
- end
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
- # Takes /etc/hosts file contents and builds a flattened entries
207
- # array so that each IP address has only one line and multiple hostnames
208
- # are flattened into a list of aliases.
209
- #
210
- # @param [Array] contents
211
- # Array of lines from /etc/hosts file
212
- def collect_and_flatten(contents)
213
- contents.each do |line|
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
- append(
218
- ip_address: entry.ip_address,
219
- hostname: entry.hostname,
220
- aliases: entry.aliases,
221
- comment: entry.comment,
222
- priority: !entry.calculated_priority? && entry.priority,
223
- )
224
- end
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
- # Removes duplicate hostnames in other files ensuring they are unique
228
- #
229
- # @param [Entry] entry
230
- # the entry to keep the hostname and aliases from
231
- #
232
- # @return [nil]
233
- def remove_existing_hostnames(entry)
234
- @entries.delete(entry)
235
- changed_hostnames = [entry.hostname, entry.aliases].flatten.uniq
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
- @entries = @entries.collect do |entry|
238
- entry.hostname = nil if changed_hostnames.include?(entry.hostname)
239
- entry.aliases = entry.aliases - changed_hostnames
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
- if entry.hostname.nil?
242
- if entry.aliases.empty?
243
- nil
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
- end.compact
280
+ else
281
+ entry
282
+ end
283
+ end.compact
252
284
 
253
- @entries << entry
285
+ @entries << entry
254
286
 
255
- nil
256
- end
287
+ nil
288
+ end
257
289
  end
258
290
 
259
291
  end
@@ -1,5 +1,5 @@
1
1
  # Copyright 2013-14, Tnarik Innael
2
2
 
3
3
  module Hostsfile
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.2"
5
5
  end
@@ -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
@@ -1,75 +1,9 @@
1
+ require 'fakefs/spec_helpers'
2
+
1
3
  describe Hostsfile do
2
4
  describe "::VERSION" do
3
- it "exists" do
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.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
- - tnarik
7
+ - Tnarik Innael
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-13 00:00:00.000000000 Z
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