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 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