lib/kuzushi.rb in kuzushi-0.0.1 vs lib/kuzushi.rb in kuzushi-0.0.2
- old
+ new
@@ -1,4 +1,225 @@
+require 'rubygems'
require 'json'
+require 'restclient'
+require 'ostruct'
class Kuzushi
+ def initialize(url)
+ @base_url = File.dirname(url)
+ @name = File.basename(url)
+ @config_names = []
+ @config = []
+ @packages = []
+ @tasks = []
+ load_config_stack(@name)
+ @merge = @config.reverse.inject({}) { |i,c| i.merge(c) }
+ process_stack
+ end
+
+ def load_config_stack(name)
+ @config_names << name
+ @config << JSON.parse(RestClient.get("#{@base_url}/#{name}"))
+ if import = @config.last["import"]
+ load_config_stack(import)
+ end
+ end
+
+ def process_stack
+ script get("before")
+
+ process :packages
+ process :local_packages
+ process :gems
+ process :volumes
+ process :raids
+ process :mounts
+ process :files
+ process :users
+
+ script get("after")
+ end
+
+ ## magic goes here
+ def process(type)
+ ## if the file takes no args - just call it once
+ if method("process_#{type}").arity == 0
+ send("process_#{type}")
+ else
+ ## else call it once per item
+ get_array(type).each do |item|
+ script item["before"]
+ if item.is_a? Hash
+ send("process_#{type}", OpenStruct.new(item))
+ else
+ send("process_#{type}", item)
+ end
+ script item["after"]
+ end
+ end
+ end
+
+ def process_packages
+ @packages = get_array("packages")
+ task "install packages" do
+ shell "apt-get update && apt-get upgrade -y && apt-get install -y #{@packages.join(" ")}", "DEBIAN_FRONTEND" => "noninteractive", "DEBIAN_PRIORITY" => "critical"
+ end
+ end
+
+ def process_local_packages(p)
+ package(p) do |file|
+ task "install local package #{p}" do
+ shell "dpkg -i #{file}"
+ end
+ end
+ end
+
+ def process_gems(gem)
+ task "install gem #{gem}" do
+ shell "gem install #{gem} --no-rdoc --no-ri"
+ end
+ end
+
+ def process_volumes(v)
+ task "wait for volume #{v.device}" do
+ wait_for_volume v.device
+ end
+ set_scheduler(v)
+ check_format(v)
+ end
+
+ def process_raids(r)
+ task "assemble raid #{r.device}" do
+ shell "mdadm --assemble #{r.device} #{r.drives.join(" ")}"
+ end
+ set_scheduler r
+ check_format r
+ add_package "mdadm"
+ end
+
+ def process_mounts(m)
+ task "mount #{m.label}" do
+ shell "mount -o #{m.options} -L #{m.label} #{m.label}"
+ end
+ end
+
+ def process_files(f)
+ fetch("/templates/#{f.template}") do |file|
+ task "setting up #{f.file}" do
+ shell "erb #{f.template} > #{f.file}" ## FIXME
+ end
+ end
+ end
+
+ def process_users(user)
+ (user.authorized_keys || []).each do |key|
+ task "add authorized_key for user #{user.name}" do
+ shell "su - #{user.name} -c 'mkdir -p .ssh; echo \"#{key}\" >> .ssh/authorized_keys; chmod -R 600 .ssh'"
+ end
+ end
+ end
+
+ def set_scheduler(v)
+ if v.scheduler
+ task "set scheduler for #{v.device}" do
+ shell "echo #{v.scheduler} > /sys/block/#{File.basename(v.device)}/queue/scheduler"
+ end
+ end
+ end
+
+ def check_format(v)
+ add_package "xfsprogs" if v.format == "xfs"
+ end
+
+ def add_package(p)
+ @packages << p unless @packages.include? p
+ end
+
+ def package(p, &block)
+ fetch("/packages/#{p}_i386.deb") do |file|
+ block.call(file)
+ end
+ end
+
+ def inline_script(script)
+ tmpfile(script) do |tmp|
+ task "run inline script" do
+ shell "#{tmp}"
+ end
+ end
+ end
+
+ def script(script)
+ return if script.nil?
+ return inline_script(script) if script =~ /^#!/
+
+ fetch("/scripts/#{script}") do |file|
+ task "run script #{script}" do
+ shell "#{file}"
+ end
+ end
+ end
+
+ def tmpfile(content, file = "tmp_#{rand(1_000_000_000)}", &block)
+ tmp_dir = "/tmp/kuzushi"
+ Dir.mkdir(tmp_dir) unless File.exists?(tmp_dir)
+ file = "#{tmp_dir}/#{File.basename(file)}"
+ File.open(file,"w") do |f|
+ f.write(content)
+ f.chmod(700)
+ end if content
+ block.call(file) if block
+ file
+ end
+
+ def fetch(file, &block)
+ names = @config_names.clone
+ begin
+ tmpfile RestClient.get("#{@base_url}/#{names.first}#{file}"), file do |tmp|
+ block.call(tmp)
+ end
+ rescue RestClient::ResourceNotFound
+ names.shift
+ retry unless names.empty?
+ error("file not found: #{file}")
+ rescue Object => e
+ error("error fetching file: #{names.first}/#{file}", e)
+ end
+ end
+
+ def error(message, exception = nil)
+ puts "ERROR :#{message}"
+ end
+
+ def get(key)
+ @merge[key.to_s]
+ end
+
+ def get_array(key)
+ [ get(key) || [] ].flatten
+ end
+
+ def wait_for_volume(vol)
+ puts "waiting for volume #{vol}"
+ end
+
+ def start
+ puts "----"
+ @tasks.each do |t|
+ puts "TASK: #{t[:description]}"
+ t[:blk].call
+ end
+ puts "----"
+ end
+
+ def shell(cmd, env = {})
+ puts " SHELL: #{cmd}"
+ end
+
+ def task(description, &blk)
+ @tasks << { :description => description, :blk => blk }
+ end
+
+ def path
+ Dir["**/config.json"]
+ end
end