sheepy.org home about

I’m a great advocator of puppet or any configuration management tool. However sometimes it’s an overkill. We have a number of servers which are not puppet controlled for various reason but we push new configurations out to them regularly.

For some things we have scripts to push the configs out and of course, some we don’t. This occasionally leads to problems.

Taking the example of a DNS server, easy to configure, easy to mess up and easy not to notice when you’ve messed up (until the TTL runs out).

The more commands you have to issue, the more likely a mistake is to be made.

There are many hammers for this nail but ruby is my weapon of choice these days so I’ve knocked up a quick Rakefile to do everything. So all I need do is rake deploy and it’ll do the following:

To avoid creating loads of SSH connections I’ve employed the Net::SSH::Multi gem which is definitly worth a look.

#
# Configuration
#
$ssh_user = "foo"
$ssh_host = "example.com"
remote_root = "/etc/bind"

rsync = 'rsync -a -e "ssh" --rsync-path="sudo rsync"'
remote_dir = "#{$ssh_user}@#{$ssh_host}:#{remote_root}/zones/"
local_dir  = "zones/"

def remote
  @remote ||= begin
    require 'net/ssh/multi'
    session = Net::SSH::Multi.start
    session.use "#{$ssh_user}@#{$ssh_host}"
    session
  end
end

desc "Get config from #{$ssh_host}"
task :get do
  puts "*** Getting config from #{$ssh_host} ***"
  system "#{rsync} #{remote_dir} #{local_dir}"
end

desc "Put config to #{$ssh_host}"
task :put do
  puts "*** Deploying config files to #{$ssh_host} ***"
  system "#{rsync} #{local_dir} #{remote_dir}"
end

desc "Set correct permissions"
task :set_perms do
  puts "*** Setting correct file permissions ***"
  remote.exec("sudo chmod 640 /etc/bind/zones/db.*").wait
  remote.exec("sudo chown bind:bind /etc/bind/zones/db.*").wait
end

desc "Test config on #{$ssh_host}"
task :test do
  puts "*** Testing zone files ***"

  errors = Array.new 

  FileList.new("zones/*").each do |zone|
    output = Array.new
    channel = remote.exec("sudo named-checkzone #{zone.sub(/zones\/db./, '')} #{remote_root}/#{zone}") do |ch, stream, data|
      output << "[#{ch[:host]} : #{stream}] #{data}"
    end

    channel.wait

    if channel.any? { |c| c[:exit_status] != 0 }
      errors << "#{zone} failed to validate"
      errors << output.join
    end

  end

  unless errors.empty?
    abort "#{errors.join("\n")}"
  end
end

desc "Reload bind on #{$ssh_host}"
task :reload do
  puts "*** Reloading Bind ***"

  channel = remote.exec("sudo rndc reload") do |ch, stream, data|
    puts "[#{ch[:host]} : #{stream}] #{data}"
  end

  channel.wait
end

desc "Put config, test and reload"
task :deploy => [:put, :set_perms, :test, :reload] do
  puts "*** Zonefiles deployed ***"
end

Yes it’s hacky and could do with some improvement but then I’m a sysadmin and not a developer. I might have a go at writting a gem to make these sort of Rakefiles cleaner but it’s not too bad.

Fork me on GitHub