lib/hostsfile/manipulator.rb in hostsfile-0.0.1 vs lib/hostsfile/manipulator.rb in hostsfile-0.0.2
- old
+ new
@@ -1,28 +1,33 @@
# Copyright 2013-14, Tnarik Innael
+#
+# Heavily based on:
# Copyright 2012-2013, Seth Vargo (from customink-webops/hostsfile/libraries/manipulator.rb)
# Copyright 2012, CustomInk, LCC
#
require 'digest/sha2'
module Hostsfile
class Manipulator
attr_reader :entries
-
# Create a new Manipulator object (aka an /etc/hosts manipulator). If a
- # hostsfile is not found, a Exception is risen, causing
- # the process to terminate on the node and the converge will fail.
+ # hostsfile is not found, a Exception is risen.
+ # Parameters are optional (see #hostsfile_path)
#
- # @param [Chef::node] node
- # the current Chef node
+ # @param [String] path
+ # the file path for the host file
+ # @param [String] family
+ # the OS family ('windows' or anything else for POSIX support)
+ # @param [String] system_directory
+ # System directory for the 'windows' family (like C:\\Windows\\system32)
# @return [Manipulator]
- # a class designed to manipulate the node's /etc/hosts file
+ # a class designed to manipulate the /etc/hosts file
def initialize(path = nil, family = nil, system_directory = nil)
# Fail if no hostsfile is found
- unless ::File.exists?(hostsfile_path)
+ unless ::File.exists?(hostsfile_path(path, family, system_directory))
raise "No hostsfile exists at '#{hostsfile_path}'!"
end
@entries = []
collect_and_flatten(::File.readlines(hostsfile_path))
@@ -111,34 +116,25 @@
end
# Save the new hostsfile to the target machine. This method will only write the
# hostsfile if the current version has changed. In other words, it is convergent.
def save
- entries = []
- entries << '#'
- entries << '# This file is managed by Chef, using the hostsfile cookbook.'
- entries << '# Editing this file by hand is highly discouraged!'
- entries << '#'
- entries << '# Comments containing an @ sign should not be modified or else'
- entries << '# hostsfile will be unable to guarantee relative priority in'
- entries << '# future Chef runs!'
- entries << '#'
- entries << ''
- entries += unique_entries.map(&:to_line)
- entries << ''
-
- contents = entries.join("\n")
- contents_sha = Digest::SHA512.hexdigest(contents)
-
# Only write out the file if the contents have changed...
- if contents_sha != current_sha
- ::File.open(hostsfile_path, 'w') do |f|
- f.write(contents)
- end
- end
+ ::File.open(hostsfile_path, 'w') do |f|
+ f.write(new_content)
+ end if content_changed?
end
+ # Determine if the content of the hostfile has changed by comparing sha
+ # values of existing file and new content
+ #
+ # @return [Boolean]
+ def content_changed?
+ new_sha = Digest::SHA512.hexdigest(new_content)
+ new_sha != current_sha
+ end
+
# Find an entry by the given IP Address.
#
# @param [String] ip_address
# the IP Address of the entry to find
# @return [Entry, nil]
@@ -150,110 +146,146 @@
end
# Determine if the current hostsfile contains the given resource. This
# is really just a proxy to {find_resource_by_ip_address} /
#
- # @param [Chef::Resource] resource
- #
+ # @param [String] ip_address
+ # the IP Address of the entry to check
# @return [Boolean]
- def contains?(resource)
- !!find_entry_by_ip_address(resource.ip_address)
+ def contains?(ip_address)
+ !!find_entry_by_ip_address(ip_address)
end
private
- # The path to the current hostsfile.
- #
- # @return [String]
- # the full path to the hostsfile, depending on the operating system
- # can also be overriden in the attributes
- def hostsfile_path (path = nil, family = nil, system_directory = nil)
- return @hostsfile_path if @hostsfile_path
- @hostsfile_path = path || case family
- when 'windows'
- "#{system_directory}\\drivers\\etc\\hosts"
- else
- '/etc/hosts'
- end
- end
+ # The path to the current hostsfile.
+ # If not path is provided, a default is guessed based on 'family' and 'system_directory'
+ # If path is provided, it takes priority
+ #
+ # @param [String] path
+ # the file path for the host file
+ # @param [String] family
+ # the OS family ('windows' or anything else for POSIX support)
+ # @param [String] system_directory
+ # System directory for the 'windows' family (default C:\\Windows\\system32)
+ # @return [String]
+ # the full path to the hostsfile, depending on the operating system
+ def hostsfile_path (path = nil, family = nil, system_directory = nil)
+ return @hostsfile_path if @hostsfile_path
+ @hostsfile_path = path || case family
+ when 'windows'
+ system_directory ||= File.join('C:','Windows','system32')
+ File.join("#{system_directory}", 'drivers', 'etc', 'hosts')
+ else
+ '/etc/hosts'
+ end
+ end
- # The current sha of the system hostsfile.
- #
- # @return [String]
- # the sha of the current hostsfile
- def current_sha
- @current_sha ||= Digest::SHA512.hexdigest(File.read(hostsfile_path))
- end
+ # The header of the new hostsfile
+ #
+ # @return [Array]
+ # an array of header comments
+ def hostsfile_header
+ lines = []
+ lines << '#'
+ lines << '# This file is managed by the hostsfile gem.'
+ lines << '# Editing this file by hand is highly discouraged!'
+ lines << '#'
+ lines << '# Comments containing an @ sign should not be modified or else'
+ lines << '# hostsfile will be unable to guarantee relative priority in'
+ lines << '# future runs!'
+ lines << '#'
+ lines << ''
+ end
- # Normalize the given list of elements into a single array with no nil
- # values and no duplicate values.
- #
- # @param [Object] things
- #
- # @return [Array]
- # a normalized array of things
- def normalize(*things)
- things.flatten.compact.uniq
- end
+ # The content that will be written to the hostfile
+ #
+ # @return [String]
+ # the full contents of the hostfile to be written
+ def new_content
+ lines = hostsfile_header
+ lines += unique_entries.map(&:to_line)
+ lines << ''
+ lines.join("\n")
+ end
- # This is a crazy way of ensuring unique objects in an array using a Hash.
- #
- # @return [Array]
- # the sorted list of entires that are unique
- def unique_entries
- entries = Hash[*@entries.map { |entry| [entry.ip_address, entry] }.flatten].values
- entries.sort_by { |e| [-e.priority.to_i, e.hostname.to_s] }
- end
+ # The current sha of the system hostsfile.
+ #
+ # @return [String]
+ # the sha of the current hostsfile
+ def current_sha
+ @current_sha ||= Digest::SHA512.hexdigest(File.read(hostsfile_path))
+ end
- # Takes /etc/hosts file contents and builds a flattened entries
- # array so that each IP address has only one line and multiple hostnames
- # are flattened into a list of aliases.
- #
- # @param [Array] contents
- # Array of lines from /etc/hosts file
- def collect_and_flatten(contents)
- contents.each do |line|
- entry = ::Hostsfile::Entry.parse(line)
- next if entry.nil?
+ # Normalize the given list of elements into a single array with no nil
+ # values and no duplicate values.
+ #
+ # @param [Object] things
+ #
+ # @return [Array]
+ # a normalized array of things
+ def normalize(*things)
+ things.flatten.compact.uniq
+ end
- append(
- ip_address: entry.ip_address,
- hostname: entry.hostname,
- aliases: entry.aliases,
- comment: entry.comment,
- priority: !entry.calculated_priority? && entry.priority,
- )
- end
+ # This is a crazy way of ensuring unique objects in an array using a Hash.
+ #
+ # @return [Array]
+ # the sorted list of entires that are unique
+ def unique_entries
+ entries = Hash[*@entries.map { |entry| [entry.ip_address, entry] }.flatten].values
+ entries.sort_by { |e| [-e.priority.to_i, e.hostname.to_s] }
+ end
+
+ # Takes /etc/hosts file contents and builds a flattened entries
+ # array so that each IP address has only one line and multiple hostnames
+ # are flattened into a list of aliases.
+ #
+ # @param [Array] contents
+ # Array of lines from /etc/hosts file
+ def collect_and_flatten(contents)
+ contents.each do |line|
+ entry = ::Hostsfile::Entry.parse(line)
+ next if entry.nil?
+
+ append(
+ ip_address: entry.ip_address,
+ hostname: entry.hostname,
+ aliases: entry.aliases,
+ comment: entry.comment,
+ priority: !entry.calculated_priority? && entry.priority,
+ )
end
+ end
- # Removes duplicate hostnames in other files ensuring they are unique
- #
- # @param [Entry] entry
- # the entry to keep the hostname and aliases from
- #
- # @return [nil]
- def remove_existing_hostnames(entry)
- @entries.delete(entry)
- changed_hostnames = [entry.hostname, entry.aliases].flatten.uniq
+ # Removes duplicate hostnames in other files ensuring they are unique
+ #
+ # @param [Entry] entry
+ # the entry to keep the hostname and aliases from
+ #
+ # @return [nil]
+ def remove_existing_hostnames(entry)
+ @entries.delete(entry)
+ changed_hostnames = [entry.hostname, entry.aliases].flatten.uniq
- @entries = @entries.collect do |entry|
- entry.hostname = nil if changed_hostnames.include?(entry.hostname)
- entry.aliases = entry.aliases - changed_hostnames
+ @entries = @entries.collect do |entry|
+ entry.hostname = nil if changed_hostnames.include?(entry.hostname)
+ entry.aliases = entry.aliases - changed_hostnames
- if entry.hostname.nil?
- if entry.aliases.empty?
- nil
- else
- entry.hostname = entry.aliases.shift
- entry
- end
+ if entry.hostname.nil?
+ if entry.aliases.empty?
+ nil
else
+ entry.hostname = entry.aliases.shift
entry
end
- end.compact
+ else
+ entry
+ end
+ end.compact
- @entries << entry
+ @entries << entry
- nil
- end
+ nil
+ end
end
end
\ No newline at end of file