From a64d484634f2e752b1f2380accf5c41bcb0b96cb Mon Sep 17 00:00:00 2001 From: Rob Lewis Date: Tue, 5 Jul 2011 16:17:11 -0400 Subject: [PATCH] Added initial impl of etc_hosts recipe --- .gitignore | 3 ++ libraries/etc_hosts.rb | 98 ++++++++++++++++++++++++++++++++++++ libraries/host.rb | 86 +++++++++++++++++++++++++++++++ libraries/network.rb | 75 +++++++++++++++++++++++++++ libraries/ssh_known_hosts.rb | 92 ++++++++++++++++----------------- metadata.rb | 3 +- recipes/default.rb | 1 + recipes/etc_hosts.rb | 33 ++++++++++++ recipes/ssh_known_hosts.rb | 2 +- 9 files changed, 346 insertions(+), 47 deletions(-) create mode 100644 .gitignore create mode 100644 libraries/etc_hosts.rb create mode 100644 libraries/host.rb create mode 100644 libraries/network.rb create mode 100644 recipes/etc_hosts.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83bc9c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.bundle/ +.idea +/bin diff --git a/libraries/etc_hosts.rb b/libraries/etc_hosts.rb new file mode 100644 index 0000000..84c38b6 --- /dev/null +++ b/libraries/etc_hosts.rb @@ -0,0 +1,98 @@ +# +# Cookbook Name:: hosts-awareness +# Library:: EtcHosts +# +# Copyright 2011, Rob Lewis +# +# 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. +# + +module HostsAwareness + class EtcHosts + attr_reader :etc_hosts_file + attr_writer :block_token + attr_accessor :use_private_addresses + + def initialize(etc_hosts_file) + @etc_hosts_file = etc_hosts_file + @use_private_addresses = false + end + + def block_token + @block_token || 'chef nodes' + end + + def set_hosts(hosts) + write_out!(hosts) + end + + def empty! + write_out!([]) + end + + protected + + def host_entry(host) + if use_private_addresses + [host.private_ipv4, host.hostname, host.short_hostname, host.provider_public_hostname, host.provider_private_hostname] + else + [host.public_ipv4, host.hostname, host.short_hostname, host.provider_public_hostname, host.provider_private_hostname] + end + end + + def format_host_entries(hosts) + host_entries = hosts.sort{|a,b| a.hostname <=> b.hostname}.collect{|host| host_entry(host)} + a = host_entries.transpose + a = a.map do |col| + w = col.map{|cell| cell.to_s.length}.max + col.map{|cell| cell.to_s.ljust(w)} + end + a.transpose.inject(''){|s, row| s << row.join(' ').rstrip + "\n"; s} + end + + def write_out!(hosts) + host_entries_string = format_host_entries(hosts) + File.open(@etc_hosts_file, 'r+') do |f| + out, over, seen_tokens = '', false, false + f.each do |line| + if line =~ /^#{start_token}/ + over = seen_tokens = true + out << line << host_entries_string + elsif line =~ /^#{end_token}/ + over = false + end + out << line unless over + end + if !seen_tokens + out << surround_with_tokens(host_entries_string) + end + + f.pos = 0 + f.print out + f.truncate(f.pos) + end + end + + def surround_with_tokens(str) + "\n#{start_token}\n" + str + "#{end_token}\n" + end + + def start_token() + "## #{block_token} start" + end + + def end_token() + "## #{block_token} end" + end + end +end diff --git a/libraries/host.rb b/libraries/host.rb new file mode 100644 index 0000000..8dde351 --- /dev/null +++ b/libraries/host.rb @@ -0,0 +1,86 @@ +# +# Cookbook Name:: hosts-awareness +# Library:: Host +# +# Copyright 2011, Rob Lewis +# +# 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. +# + +module HostsAwareness + class Host + attr_reader :network + attr_accessor :hostname + attr_accessor :private_ipv4 + attr_accessor :public_ipv4 + attr_accessor :provider_private_hostname + attr_accessor :provider_public_hostname + + def self.from_node(node, network=nil) + host = new(network) + host.hostname = node['fqdn'] || "#{node['hostname']}.#{node['domain']}" + if node['cloud'] && node['cloud']['local_ipv4'] + host.private_ipv4 = node['cloud']['local_ipv4'] + elsif node['ec2'] && node['ec2']['local_ipv4'] + host.private_ipv4 = node['ec2']['local_ipv4'] + else + host.private_ipv4 = node['ipaddress'] + end + + if node['cloud'] && node['cloud']['public_ipv4'] + host.public_ipv4 = node['cloud']['public_ipv4'] + elsif node['ec2'] && node['ec2']['public_ipv4'] + host.public_ipv4 = node['ec2']['public_ipv4'] + end + + if node['cloud'] && node['cloud']['local_hostname'] + host.provider_private_hostname = node['cloud']['local_hostname'] + elsif node['ec2'] && node['ec2']['local_hostname'] + host.provider_private_hostname = node['ec2']['local_hostname'] + end + + if node['cloud'] && node['cloud']['public_hostname'] + host.provider_public_hostname = node['cloud']['public_hostname'] + elsif node['ec2'] && node['ec2']['public_hostname'] + host.provider_public_hostname = node['ec2']['public_hostname'] + end + host + end + + def initialize(network=nil) + @network = network + end + + def short_hostname + @short_hostname ||= begin + if @network && @network.short_hostname_parts && @network.short_hostname_parts > 0 + @hostname.split('.').shift(@network.short_hostname_parts).join('.') + else + @hostname.split('.').first + end + end + end + + def domain_name + @domain_name ||= @hostname.partition('.').last + end + + def ==(other) + other.equal?(self) || (other.instance_of?(self.class) && other.hostname == @hostname) + end + + def eql?(other) + self == (other) + end + end +end diff --git a/libraries/network.rb b/libraries/network.rb new file mode 100644 index 0000000..0a65bf4 --- /dev/null +++ b/libraries/network.rb @@ -0,0 +1,75 @@ +# +# Cookbook Name:: hosts-awareness +# Library:: Network +# +# Copyright 2011, Rob Lewis +# +# 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 'chef/mixin/language' +include Chef::Mixin::Language + +module HostsAwareness + class Network + attr_reader :name + attr_accessor :provider + attr_accessor :node_search_query + attr_accessor :short_hostname_parts + + def self.from_data_bag(network_name) + network = HostsAwareness::Network.new(network_name) + all_network_names = data_bag('networks') + if all_network_names.include?(network_name) + network_data_bag_item = data_bag_item('networks', network_name) + network.provider = network_data_bag_item['provider'] + network.node_search_query = network_data_bag_item['node_search_query'] + network.short_hostname_parts = network_data_bag_item['short_hostname_parts'].to_i + else + Chef::Log.error("No data bag entry for network \"#{network_name}\"") + end + network + end + + def initialize(network_name) + @name = network_name + end + + def hosts + @hosts ||= begin + hosts = nodes.collect{|n| HostsAwareness::Host.from_node(n, self)} + hosts.uniq + end + end + + def nodes + @nodes ||= @node_search_query.nil? ? [] : search(:node, @node_search_query) + end + + def member?(node_or_host) + if node_or_host.is_a?(HostsAwareness::Host) + hosts.any?{|host| host == node_or_host} + else + nodes.any?{|node| node.name == node_or_host.name} + end + end + + def ==(other) + other.equal?(self) || (other.instance_of?(self.class) && other.name == @name) + end + + def eql?(other) + self == (other) + end + end +end diff --git a/libraries/ssh_known_hosts.rb b/libraries/ssh_known_hosts.rb index d271260..d4e5056 100644 --- a/libraries/ssh_known_hosts.rb +++ b/libraries/ssh_known_hosts.rb @@ -17,61 +17,63 @@ # limitations under the License. # -class SshKnownHosts - attr_reader :known_hosts_file - attr_accessor :start_token - attr_accessor :end_token +module HostsAwareness + class SshKnownHosts + attr_reader :known_hosts_file + attr_accessor :start_token + attr_accessor :end_token - def initialize(known_hosts_file, start_token=nil, end_token=nil) - @known_hosts_file = known_hosts_file - @start_token = start_token || '# chef nodes start' - @end_token = end_token || '# chef nodes end' - end + def initialize(known_hosts_file, start_token=nil, end_token=nil) + @known_hosts_file = known_hosts_file + @start_token = start_token || '# chef nodes start' + @end_token = end_token || '# chef nodes end' + end - def set_hosts(hosts) - write_out!(hosts) - end + def set_hosts(hosts) + write_out!(hosts) + end - def empty! - write_out!([]) - end + def empty! + write_out!([]) + end - protected + protected - def write_out!(hosts) - new_ghosts = hosts.inject('') do |s, host| - fqdn = host['fqdn'] - unless fqdn.nil? - rsa_public_ssh_key = host['keys']['ssh']['host_rsa_public'] - s += "#{fqdn},#{host['ipaddress']} ssh-rsa #{rsa_public_ssh_key}\n" unless rsa_public_ssh_key.nil? - dsa_public_ssh_key = host['keys']['ssh']['host_rsa_public'] - s += "#{fqdn},#{host['ipaddress']} ssh-dsa #{dsa_public_ssh_key}\n" unless dsa_public_ssh_key.nil? + def write_out!(hosts) + new_ghosts = hosts.inject('') do |s, host| + fqdn = host['fqdn'] + unless fqdn.nil? + rsa_public_ssh_key = host['keys']['ssh']['host_rsa_public'] + s += "#{fqdn},#{host['ipaddress']} ssh-rsa #{rsa_public_ssh_key}\n" unless rsa_public_ssh_key.nil? + dsa_public_ssh_key = host['keys']['ssh']['host_rsa_public'] + s += "#{fqdn},#{host['ipaddress']} ssh-dsa #{dsa_public_ssh_key}\n" unless dsa_public_ssh_key.nil? + end + s end - s - end - File.open(@known_hosts_file, 'r+') do |f| - out, over, seen_tokens = '', false, false - f.each do |line| - if line =~ /^#{@start_token}/o - over = seen_tokens = true - out << line << new_ghosts - elsif line =~ /^#{@end_token}/o - over = false + File.open(@known_hosts_file, 'r+') do |f| + out, over, seen_tokens = '', false, false + f.each do |line| + if line =~ /^#{@start_token}/o + over = seen_tokens = true + out << line << new_ghosts + elsif line =~ /^#{@end_token}/o + over = false + end + out << line unless over + end + if !seen_tokens + out << surround_with_tokens(new_ghosts) end - out << line unless over - end - if !seen_tokens - out << surround_with_tokens(new_ghosts) - end - f.pos = 0 - f.print out - f.truncate(f.pos) + f.pos = 0 + f.print out + f.truncate(f.pos) + end end - end - def surround_with_tokens(str) - "\n#{@start_token}\n" + str + "#{@end_token}\n" + def surround_with_tokens(str) + "\n#{@start_token}\n" + str + "#{@end_token}\n" + end end end diff --git a/metadata.rb b/metadata.rb index b5072fd..ea3c743 100644 --- a/metadata.rb +++ b/metadata.rb @@ -4,9 +4,10 @@ license 'Apache 2.0' description 'Installs/Configures hosts-awareness' long_description IO.read(File.join(File.dirname(__FILE__), 'README.rdoc')) -version '0.0.1' +version '0.0.3' recipe 'hosts-awareness', 'Includes all recipes' +recipe 'hosts-awareness::etc_hosts', 'Updates /etc/hosts with node entries.' recipe 'hosts-awareness::ssh_known_hosts', 'Updates ssh/known_hosts with node entries.' attribute 'hosts-awareness/ssh_known_hosts/file_owner', diff --git a/recipes/default.rb b/recipes/default.rb index 59cbb4e..83bf57b 100644 --- a/recipes/default.rb +++ b/recipes/default.rb @@ -17,4 +17,5 @@ # limitations under the License. # +include_recipe 'hosts-awareness::etc_hosts' include_recipe 'hosts-awareness::ssh_known_hosts' diff --git a/recipes/etc_hosts.rb b/recipes/etc_hosts.rb new file mode 100644 index 0000000..5defb74 --- /dev/null +++ b/recipes/etc_hosts.rb @@ -0,0 +1,33 @@ +# +# Cookbook Name:: hosts-awareness +# Recipe:: etc_hosts +# +# Copyright 2011, Rob Lewis +# +# 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. +# + +ruby_block 'update_etc_hosts' do + block do + all_network_names = data_bag('networks') + networks = node['hosts_awareness'].nil? ? [] : Array(node['hosts_awareness']['networks']) + networks = all_network_names if networks.first == 'all' + networks.each do |network_name| + network = HostsAwareness::Network.from_data_bag(network_name) + etc_hosts = HostsAwareness::EtcHosts.new('/etc/hosts') + etc_hosts.block_token = network_name + etc_hosts.use_private_addresses = network.member?(node) + etc_hosts.set_hosts(network.hosts) + end + end +end diff --git a/recipes/ssh_known_hosts.rb b/recipes/ssh_known_hosts.rb index 62ecd1c..0d71451 100644 --- a/recipes/ssh_known_hosts.rb +++ b/recipes/ssh_known_hosts.rb @@ -49,6 +49,6 @@ ruby_block 'write_ssh_known_hosts' do block do - SshKnownHosts.new(known_hosts_file).set_hosts(nodes) + HostsAwareness::SshKnownHosts.new(known_hosts_file).set_hosts(nodes) end end