2021-01-22 18:41:45 +01:00

187 lines
5.5 KiB
Ruby

#
# Author:: Seth Vargo <sethvargo@gmail.com>
# Cookbook:: hostsfile
# Library:: entry
#
# Copyright:: 2012-2013, Seth Vargo
# Copyright:: 2012, CustomInk, LCC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'ipaddr'
# An object representation of a single line in a hostsfile.
#
# @author Seth Vargo <sethvargo@gmail.com>
module HostsFile
class Entry
class << self
# Creates a new Hostsfile::Entry object by parsing a text line. The
# `line` attribute will be in the following format:
#
# 1.2.3.4 hostname [alias[, alias[, alias]]] [# comment [@priority]]
#
# @param [String] line
# the line to parse
# @return [Entry]
# a new entry object
def parse(line)
entry, comment = extract_comment(line)
comment, priority = extract_priority(comment)
entries = extract_entries(entry)
# Return nil if the line is empty
return nil if entries.nil? || entries.empty?
# If /etc/hosts has a broken content we throw a descriptive exception
if entries[0].nil?
raise ArgumentError, "/etc/hosts has a line without IP address: #{line}"
end
if entries[1].nil?
raise ArgumentError, "/etc/hosts has a line without hostname: #{line}"
end
new(
ip_address: entries[0],
hostname: entries[1],
aliases: entries[2..-1],
comment: comment,
priority: priority
)
end
private
def extract_comment(line)
return nil if presence(line).nil?
line.split('#', 2).collect { |part| presence(part) }
end
def extract_priority(comment)
return nil if comment.nil?
if comment.include?('@')
comment.split('@', 2).collect { |part| presence(part) }
else
[comment, nil]
end
end
def extract_entries(entry)
return nil if entry.nil?
entry.split(/\s+/).collect { |entry| presence(entry) }.compact
end
def presence(string)
return nil if string.nil?
return nil if string.strip.empty?
string.strip
end
end
# @return [String]
attr_accessor :ip_address, :hostname, :aliases, :comment, :priority
# Creates a new entry from the given options.
#
# @param [Hash] options
# a list of options to create the entry with
# @option options [String] :ip_address
# the IP Address for this entry
# @option options [String] :hostname
# the hostname for this entry
# @option options [String, Array<String>] :aliases
# a alias or array of aliases for this entry
# @option options[String] :comment
# an optional comment for this entry
# @option options [Integer] :priority
# the relative priority of this entry (compared to others)
#
# @raise [ArgumentError]
# if neither :ip_address nor :hostname are supplied
def initialize(options = {})
if options[:ip_address].nil? || options[:hostname].nil?
raise ArgumentError, ':ip_address and :hostname are both required options'
end
@ip_address = IPAddr.new(remove_ip_scope(options[:ip_address]))
@hostname = options[:hostname]
@aliases = [options[:aliases]].flatten.compact
@comment = options[:comment]
@priority = options[:priority] || calculated_priority
end
# Set a the new priority for an entry.
#
# @param [Integer] new_priority
# the new priority to set
def priority=(new_priority)
@calculated_priority = false
@priority = new_priority
end
# The line representation of this entry.
#
# @return [String]
# the string representation of this entry
def to_line
hosts = [hostname, aliases].flatten.join(' ')
comments = "# #{comment}".strip
comments << " @#{priority}" unless priority.nil? || @calculated_priority
comments = comments.strip
comments = nil if comments == '#'
[ip_address, hosts, comments].compact.join("\t").strip
end
# Returns true if priority is calculated
#
# @return [Boolean]
# true if priority is calculated and false otherwise
def calculated_priority?
@calculated_priority
end
private
# Calculates the relative priority of this entry.
#
# @return [Integer]
# the relative priority of this item
def calculated_priority
@calculated_priority = true
return 82 if ip_address == IPAddr.new('127.0.0.1')
return 81 if ip_address == IPAddr.new('::1')
return 80 if IPAddr.new('127.0.0.0/8').include?(ip_address) # local
return 60 if ip_address.ipv4? # ipv4
return 20 if ip_address.ipv6? # ipv6
00
end
# Removes the scopes pieces of the address, because reasons.
#
# @see https://bugs.ruby-lang.org/issues/8464
# @see https://github.com/customink-webops/hostsfile/issues/51
#
# @return [String, nil]
#
def remove_ip_scope(address)
return nil if address.nil?
address.to_s.sub(/%.*/, '')
end
end
end