public
Description: Dependency Resolution Framework
Home | Edit | New

An example resolver


# As an example here is the FileResolver

class WarningShot::FileResolver
  include WarningShot::Resolver

  # What is the add_dependency garbage?  Where is require?
  # All core libraries and gems should be 'required' with add_dependency, this allows warningshot to 'pick' it dependencies.
  #  This makes warningshot minimalistic.  For example, if 'net/scp' was required for FileResolver (which it will in the future)
  #  you could still use warningshot without it, it would simply disable the use of scp_protocol resolution.  By default
  # if a dependency is missing warningshot will disable the whole Resolver (ie, fileutils and uri below).  This is because they are
  # critical in how the FileResolver works.  The additional dependencies are optional based on how warningshot is being used.
  add_dependency :core, 'fileutils'
  add_dependency :core, 'uri'
  add_dependency :core, 'net/http', :disable => false, :unregister => :http_protocol_resolver
  add_dependency :core, 'net/https', :disable => false, :unregister => :https_protocol_resolver

  # The default sequential order for the resolver to run in, this can be modified
  #   by the end user by FileResolver.order(1) or by specifying the order in
  #   the priority loader.
  #   WarningShot::Config.new({:pload => [:file]})
  order  500

  # The branch of the dependency tree to use, in the near future multiple branches will be
  #    able to be specified
  branch :file

  # A description of what the resolver does
  description 'Validates presence of files'

  # A resource needs to be created to 'stuff' the YAML into.  Why?
  #   Because additional methods are added to track the status of tests and
  #   and resolution, so the dependency needs to be a mutable object.
  FileResource = Struct.new(:source,:target) do
    def exists?;File.exists?(File.expand_path(target.path));end;
    def remove;File.unlink(File.expand_path(target.path));end;
  end

  #  Resolver.typecast is what puts the YAML into the resource (above).
  #    typecast takes a class (optional) and a block multiple typecasts only need
  #    to be declared if there are multiple datatypes per dependency in the yaml file.
  #  FileResource can handle a String or a Hash.  The class is optional, so if there is only
  #    one type of data in your yaml, you can leave it off
  typecast String do |file|
    FileResource.new URI.parse(''), URI.parse(file)
  end

  #  Handling a hash from yaml
  typecast Hash do |file|
    file[:source].sub!(/file:\/\//i,'') unless file[:source].nil?
    FileResource.new URI.parse(file[:source] || ''), URI.parse(file[:target])
  end

  # Resolver#initialize can be defined.  It will be passed one arg and a variable list argument.
  #   Make sure to call super in the constructor
  #
  # there is also an attr_reader :config that will be available to the object
  #   and an attr_accessor :dependencies which stores the set of dependencies to be checked
  def initialize(config,*deps)
    super
  end

  # Multiple tests can be registered.  The first test to pass the :if or :unless block (if provided)
  #   will be executed, if none pass it, then a default test will be run (one without a conditional block).
  #   Examples of conditional blocks can be seen in register :resolution below
  #
  # The actual test block (after do) can take 0, 1, or 2 arguments.  The Resolver will pass in the correct number
  #   attributes.  If one is specified it will pass in the current dependency (at runtime).  If two are specified it will
  #   pass in the current dependency and the running configuration.
  #   
  #  @example
  #    #A test taking a file dependency and a configuration
  #    register(:test) do |file,config|
  #      if file.target.path =~ /database.yml/ && config[:environment] == 'production'
  #        # ... do some specific test for production database config files
  #      end
  #    end
  #
  #    #A test with an :if conditional that takes the configuration...
  #    register(:test,:if => lambda{|file,config| config[:environment] == 'development' }) do |file|
  #      # ... the :if statement only allows this to run in the 'development' environment
  #      # ... in this test I may only need file and not the config.
  #    end
  #
  register :test, {:name => :file_check} do |file|
    if file_found = file.exists?
      logger.debug " ~ [PASSED] file: #{file.target.path}" 
    else
      logger.warn " ~ [FAILED] file: #{file.target.path}" 
    end

    file_found
  end

  # Register a resolution.  The :if condition checks to see that the protocol to resolve over
  #   is file://
  register(:resolution, { :name => :file_protocol,
    :desc => "Resolves files from target sources",
    :if => lambda { |file| 
      !!(file.source.scheme =~ /file/i || file.source.scheme == nil && !file.source.path.empty?)
    }
  }) do |file|    
    begin
      FileUtils.cp File.expand_path(file.source.path), File.expand_path(file.target.path)
    rescue Exception => ex
      logger.error " ~ Could not restore file (#{file.target.path}) from #{file.source.path}" 
    end
    file.exists?
  end

  # Register a resolution.  The :if condition checks to see that the protocol to resolve over
  #   is http:// or https://
  register(:resolution, { :name => :http_protocol,
    :desc => "Resolves files from HTTP sources",
    :if => lambda { |file| !!(file.source.to_s =~ /http(s)?/i)}
  }) do |file|
    begin
      http = Net::HTTP.new(file.source.host,file.source.port)
      http.use_ssl = (file.source.scheme == 'https')
      file.source.path = '/' if file.source.path.empty?
      resp = http.get(file.source.path)

      File.open(file.target.path,"w+"){ |fs| fs.puts resp.body }  if resp.code == "200" 
    rescue Exception => ex  
      logger.error " ~ Could not restore file (#{file.target.path}) from #{file.source.path}" 
    end
    file.exists?
  end

end
Last edited by coryodaniel, Fri Nov 21 18:01:32 -0800 2008
Home | Edit | New
Versions: