class Quilt

Constants

ARCHIVE_SUFFIX
COMMON_KEY
DEBUG_PREFIX_KEY
DEFAULT_LRU_SIZE
DEPENDANCIES_KEY
HEADER_KEY
OPTIONAL_KEY
POSITION_KEY
PREFIX_KEY
VALID_POSITIONS

Public Class Methods

new(config = "quilt", log = Logger.new(STDOUT)) click to toggle source
# File lib/quilt.rb, line 23
def initialize(config = "quilt", log = Logger.new(STDOUT))
  @@fetching_versions = {}
  config_prefix = config ? "#{config}:" : ""
  @config = {
    :local_path => Ecology.property("#{config_prefix}local_path", :as => String),
    :dev_path => Ecology.property("#{config_prefix}dev_path", :as => String),
    :remote_host => Ecology.property("#{config_prefix}remote_host", :as => String),
    :remote_path => Ecology.property("#{config_prefix}remote_path", :as => String),
    :remote_port => Ecology.property("#{config_prefix}remote_port", :as => String),
    :lru_size => Ecology.property("#{config_prefix}lru_size", :as => Fixnum)
  };
  @versions = LRUCache.new(@config[:lru_size] ? @config[:lru_size] : DEFAULT_LRU_SIZE)
  @log = log

  if (@config[:local_path])
    Dir.foreach(@config[:local_path]) do |version_dir|
      next if version_dir == "." || version_dir == ".."
      @versions[version_dir] = load_version(@config[:local_path], version_dir)
    end
  else
    throw "Quilt: local path not specified";
  end
end

Public Instance Methods

fetch_remote(file) click to toggle source
# File lib/quilt.rb, line 373
def fetch_remote(file)
  host = @config[:remote_host].to_s
  port = @config[:remote_port] ? @config[:remote_port].to_i : 80
  path = File.join(@config[:remote_path].to_s, file)
  conn = Faraday.new(:url => "http://#{host}:#{port}") do |faraday|
    faraday.adapter Faraday.default_adapter
  end
  conn.get(path)
end
get_module(filename, dependancies, position, version_dir) click to toggle source
# File lib/quilt.rb, line 68
def get_module(filename, dependancies, position, version_dir)
  tmp_module = {}
  tmp_module[:position] = position
  tmp_module[:dependancies] = dependancies.is_a?(Array) ? dependancies :
                                                          (dependancies.is_a?(String) ? [ dependancies ] :
                                                          [])
  begin
    tmp_module[:module] = File.open(File.join(version_dir, filename), "rb").read
  rescue Exception => e
    log_error("  Could not load module: #{filename}", e)
    return nil
  end
  tmp_module
end
get_module_name(filename) click to toggle source
# File lib/quilt.rb, line 61
def get_module_name(filename)
  return nil unless filename
  matches = filename.match(/(^.*\/|^)([^\/]+)$/)
  return nil unless matches && matches.length >= 3
  matches[2]
end
get_version(name) click to toggle source
# File lib/quilt.rb, line 242
def get_version(name)
  return @versions[name] if @versions[name]
  if (name == 'dev' && @config[:dev_path])
    return load_version(@config[:dev_path], '')
  end
  # check local path
  # sleep at most 10 seconds to wait until a version is fetched and untared
  @@fetching_versions[name] ||= Mutex.new
  @@fetching_versions[name].synchronize do
    if version_exists_locally?(@config[:local_path], name)
      @versions[name] = load_version(@config[:local_path], name)
      return @versions[name] if @versions[name]
    end
    # check remote path
    if (!@config[:remote_host] || !@config[:remote_path])
      log_error("unable to load from host: #{@config[:remote_host]}, path: #{@config[:remote_path]}")
      return nil
    end
    # Fetch the version
    filename = "#{name}#{ARCHIVE_SUFFIX}"
    version_dir = File.join(@config[:local_path], name)
    begin
      res = fetch_remote(filename)
      if (res.status.to_i != 200)
        log_error("no version fetched : #{res.env[:url].to_s} - status #{res.status.to_i}")
        return nil
      end
      FileUtils.mkdir(version_dir) unless File.exists?(version_dir)
      open(File.join(version_dir, filename), "wb") do |file|
        file.write(res.body)
      end
    rescue Exception => e
      log_error("could not fetch version", e)
      return nil
    end
    # Untar the version
    tar_stdout = nil
    tar_stderr = nil
    tar_status =
      POpen4::popen4("cd #{version_dir} && tar -xzf #{filename} && rm #{filename}") do |stdo, stde, stdi, pid|
      stdi.close
      tar_stdout = stdo.read.strip
      tar_stderr = stde.read.strip
    end
    if (!tar_status.exitstatus.zero?)
      log_error("unable to untar package: cd #{version_dir} && tar -xzf #{filename} && rm #{filename}")
      log_error("stdout: #{tar_stdout}")
      log_error("stderr: #{tar_stderr}")
      begin
        FileUtils.rm_r(version_dir)
      rescue Exception => e
        # do nothing
      end
      return nil
    end
    # Load the version
    @versions[name] = load_version(@config[:local_path], name)
    if (!@versions[name])
      begin
        FileUtils.rm_r(version_dir)
      rescue Exception => e
        # do nothing
      end
      return nil
    end
  end
  @@fetching_versions.reject! do |key, value|
    key == name
  end
  @versions[name]
end
health() click to toggle source
# File lib/quilt.rb, line 383
def health
  # return true if no remote info
  return [true, nil, nil] if !@config[:remote_host] || !@config[:remote_path]
  # fetch health_check.txt from remote URL
  begin
    res = fetch_remote('health_check.txt')
    if (res.status.to_i != 200)
      return [false,
              "Could not fetch heath check file: #{res.env[:url].to_s} - status #{res.status.to_i}",
              nil]
    end
  rescue Exception => e
    return [false, "Could not fetch heath check file - #{e.message}", e]
  end
  [true, nil, nil]
end
load_version(local_path, version_name) click to toggle source
# File lib/quilt.rb, line 91
def load_version(local_path, version_name)
  log_debug("Loading Version: "+version_name)
  manifest = {}
  new_version = {
    :name => version_name,
    :dir => File.join(local_path, version_name),
    :default => {
      :header => '',
      :common => '',
      :optional => {},
      :footer => '',
      :base_modules => []
    }
  }
  begin
    manifest = JSON.parse(File.read(File.join(new_version[:dir], "manifest.json")))
    new_version[:default][:dir] =
      manifest[PREFIX_KEY] ? File.join(new_version[:dir], manifest[PREFIX_KEY]) :
                             new_version[:dir]
    if (manifest[DEBUG_PREFIX_KEY])
      new_version[:debug] = {
        :dir => manifest[DEBUG_PREFIX_KEY] ?  File.join(new_version[:dir], manifest[DEBUG_PREFIX_KEY]) :
                                              new_version[:dir],
        :header => '',
        :common => '',
        :optional => {},
        :footer => '',
        :base_modules => []
      }
    end
  rescue Exception => e
    log_error("  Could not read manifest!", e);
    return nil
  end
  #  manifest.json:
  #  {
  #    "prefix" : "<prefix directory>"
  #    "debug_prefix : "<debug prefix directory"
  #    "header" : "<header file>",
  #    "footer" : "<footer file>",
  #    "common" : [
  #      "<module file>",
  #      { <module file> : "<position>" },
  #      ...
  #    ],
  #    "optional" : {
  #      "<module file>" : [ "<dependancy module name>", ... ],
  #      "<module file>" : { "dependancies" : [ "<dependancy module name>", ... ], "position" : "<position>" }
  #      ...
  #    }
  #  }
  #
  #  where <position> can be one of: before_header, after_header, before_common, after_common,
  #                                  before_optional, optional, after_optional, before_footer, after_footer
  module_loader = Proc.new do |prefix|
    dir = new_version[prefix][:dir]
    if manifest[HEADER_KEY]
      begin
        new_version[prefix][:header] = File.open(File.join(dir, manifest[HEADER_KEY]), "rb").read
      rescue Exception => e
        log_error("  Could not load #{prefix.to_s} header: #{manifest[HEADER_KEY]}", e)
      end
    end
    if manifest[COMMON_KEY] && manifest[COMMON_KEY].is_a?(Array)
      manifest[COMMON_KEY].each do |filename|
        if (filename.is_a?(String))
          begin
            new_version[prefix][:common] =
              "#{new_version[prefix][:common]}#{File.open(File.join(dir, filename), "rb").read}"
          rescue Exception => e
            log_error("  Could not load #{prefix.to_s} common module: #{filename}", e)
          end
        elsif (filename.is_a?(Hash))
          filename.each do |actual_name, position_str|
            position = position_str && VALID_POSITIONS.include?(position_str) ?
                         position_str.to_sym : :before_common
            tmp_module_name = get_module_name(actual_name)
            if (tmp_module_name)
              tmp_module = get_module(actual_name, [], position, dir)
              if (tmp_module)
                new_version[prefix][:optional][tmp_module_name] = tmp_module
                new_version[prefix][:base_modules].push(tmp_module_name)
              end
            else
              log_error("  Could not extract #{prefix.to_s} module name from: #{filename}")
            end
          end
        end
      end
    end
    if manifest[OPTIONAL_KEY] && manifest[OPTIONAL_KEY].is_a?(Hash)
      manifest[OPTIONAL_KEY].each do |filename, value|
        dependancies = []
        position = :optional
        if (value.is_a?(Array))
          dependancies = value
        elsif (value.is_a?(Hash))
          dependancies = value[DEPENDANCIES_KEY] || []
          position = value[POSITION_KEY] && VALID_POSITIONS.include?(value[POSITION_KEY]) ?
                       value[POSITION_KEY].to_sym : :optional
        end
        tmp_module_name = get_module_name(filename)
        if (tmp_module_name)
          tmp_module = get_module(filename, dependancies, position, dir)
          if (tmp_module)
            new_version[prefix][:optional][tmp_module_name] = tmp_module
          end
        else
          log_error("  Could not extract #{prefix.to_s} module name from: #{filename}")
        end
      end
    end
    if manifest[FOOTER_KEY]
      begin
        new_version[prefix][:footer] = File.open(File.join(dir, manifest[FOOTER_KEY]), "rb").read
      rescue Exception => e
        log_error("  Could not load #{prefix.to_s} footer: #{manifest[FOOTER_KEY]}", e)
        new_version[:footer] = ''
      end
    end
  end

  module_loader.call(:default)
  module_loader.call(:debug) if new_version[:debug]

  new_version
end
log_debug(msg) click to toggle source
# File lib/quilt.rb, line 56
def log_debug(msg)
  return unless @log && @log.debug?
  @log.debug(msg)
end
log_error(msg, e = nil) click to toggle source
# File lib/quilt.rb, line 47
def log_error(msg, e = nil)
  return unless @log && @log.error?
  @log.error(msg) if msg
  if (e)
    @log.error(e.message)
    @log.error(e.backtrace.join("\n"))
  end
end
resolve_dependancies(position, modules, version, all_modules = {}) click to toggle source
# File lib/quilt.rb, line 219
def resolve_dependancies(position, modules, version, all_modules = {})
  out = ''
  return out if !modules || !(modules.is_a?(Array)) || modules.empty?
  my_all_modules = all_modules
  modules.each do |name|
    next if my_all_modules[name] == 2
    if (!version[:optional][name] || !version[:optional][name][:module])
      log_error("  invalid module: #{name}");
      my_all_modules[name] = 2
      next
    end
    if (my_all_modules[name] == 1)
      log_error("  circular module dependancy: #{name}")
      next
    end
    my_all_modules[name] = 1
    out = "#{out}#{resolve_dependancies(position, version[:optional][name][:dependancies], version, my_all_modules)}"
    out = "#{out}#{version[:optional][name][:module]}" if version[:optional][name][:position] == position
    my_all_modules[name] = 2
  end
  out
end
resolve_dynamic(position, modules, dynamics, version) click to toggle source
# File lib/quilt.rb, line 368
def resolve_dynamic(position, modules, dynamics, version)
  dynamics[position].is_a?(Array) ? resolve_dependancies(position, dynamics[position] + modules, version, {}) :
    "#{(dynamics[position] || '')}#{resolve_dependancies(position, modules, version, {})}"
end
status() click to toggle source
# File lib/quilt.rb, line 400
  def status
    remote_url = "Remote URL: none"
    if (@config[:remote_host] && @config[:remote_path])
      host = @config[:remote_host].to_s
      port = @config[:remote_port] ? @config[:remote_port].to_i : 80
      path = File.join(@config[:remote_path].to_s, "[version]#{ARCHIVE_SUFFIX}")
      remote_url = "Remote URL: http://#{host}:#{port}#{path}"
    end
    body = <<EOS
-- Quilt Status --
#{remote_url}
Locally Available Versions:
  #{@versions.keys.join("\n  ")}
EOS
    body
  end
stitch(selector, version_name, prefix = :default, dynamic_modules = nil) click to toggle source
# File lib/quilt.rb, line 314
def stitch(selector, version_name, prefix = :default, dynamic_modules = nil)
  return nil if !selector
  version = get_version(version_name)
  if (!version)
    log_error("could not fetch version: #{version_name}")
    return nil
  end
  outversion = version[prefix] || version[:default]

  # get modules we want to use
  modules = []
  if (selector.is_a?(Proc))
    modules = outversion[:optional].keys.select do |mod|
      selector.call(mod)
    end
  elsif (selector.is_a?(Array))
    modules = selector
  end
  base_modules = []
  outversion[:base_modules].each do |mod|
    next if modules.include?(mod)
    base_modules.push(mod)
  end
  modules = base_modules.concat(modules)

  # resolve dependancies
  dynamics = dynamic_modules && dynamic_modules.is_a?(Hash) ? dynamic_modules : {}
  output = "#{
    resolve_dynamic(:before_header, modules, dynamics, outversion)
  }#{
    outversion[:header]
  }#{
    resolve_dynamic(:after_header, modules, dynamics, outversion)
  }#{
    resolve_dynamic(:before_common, modules, dynamics, outversion)
  }#{
    outversion[:common]
  }#{
    resolve_dynamic(:after_common, modules, dynamics, outversion)
  }#{
    resolve_dynamic(:before_optional, modules, dynamics, outversion)
  }#{
    resolve_dynamic(:optional, modules, dynamics, outversion)
  }#{
    resolve_dynamic(:after_optional, modules, dynamics, outversion)
  }#{
    resolve_dynamic(:before_footer, modules, dynamics, outversion)
  }#{
    outversion[:footer]
  }#{
    resolve_dynamic(:after_footer, modules, dynamics, outversion)
  }"
end
version_exists_locally?(local_path, version_name) click to toggle source
# File lib/quilt.rb, line 83
def version_exists_locally?(local_path, version_name)
  begin
    manifest = JSON.parse(File.read(File.join(File.join(local_path, version_name), "manifest.json")))
  rescue Exception => e
    return false
  end
end