root/lib/cyborghood/cyborg/botnet/interface.rb @ 7988a0ad
d1e614b5 | Marc Dequènes (Duck) | #--
|
|
# CyborgHood, a distributed system management software.
|
|||
364e4a96 | Marc Dequènes (Duck) | # Copyright (c) 2009-2011 Marc Dequènes (Duck) <Duck@DuckCorp.org>
|
|
d1e614b5 | Marc Dequènes (Duck) | #
|
|
# This program is free software: you can redistribute it and/or modify
|
|||
# it under the terms of the GNU General Public License as published by
|
|||
# the Free Software Foundation, either version 3 of the License, or
|
|||
# (at your option) any later version.
|
|||
#
|
|||
# This program is distributed in the hope that it will be useful,
|
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
# GNU General Public License for more details.
|
|||
#
|
|||
# You should have received a copy of the GNU General Public License
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#++
|
|||
require 'singleton'
|
|||
7466fc08 | Marc Dequènes (Duck) | require 'ostruct'
|
|
d1e614b5 | Marc Dequènes (Duck) | ||
module CyborgHood
|
|||
7466fc08 | Marc Dequènes (Duck) | module DSL
|
|
class ServerApiNode < BaseDSL
|
|||
4761cd13 | Marc Dequènes (Duck) | attr_reader :bot, :node_name, :parent_node, :store
|
|
8fcaacd6 | Marc Dequènes (Duck) | ||
66cff75f | Marc Dequènes (Duck) | include TaskAspect
|
|
7466fc08 | Marc Dequènes (Duck) | # needed for testing node existence
|
|
reveal :nil?
|
|||
4761cd13 | Marc Dequènes (Duck) | reveal :respond_to?
|
|
7988a0ad | Marc Dequènes (Duck) | reveal :is_a?
|
|
476368e8 | Marc Dequènes (Duck) | ||
eab0c364 | Marc Dequènes (Duck) | def initialize(bot, parent_node = nil, options = {}, &block)
|
|
7466fc08 | Marc Dequènes (Duck) | @bot = bot
|
|
@parent_node = parent_node
|
|||
eab0c364 | Marc Dequènes (Duck) | @ldir = options[:dir]
|
|
@hidden = options[:hidden] || false
|
|||
4761cd13 | Marc Dequènes (Duck) | ||
7466fc08 | Marc Dequènes (Duck) | # don't call super because we need defered loading
|
|
4761cd13 | Marc Dequènes (Duck) | ||
@blocks = []
|
|||
add_behavior(&block)
|
|||
@ldir_loaded = false
|
|||
a3483167 | Marc Dequènes (Duck) | ||
7466fc08 | Marc Dequènes (Duck) | cleanup
|
|
a3483167 | Marc Dequènes (Duck) | end
|
|
8fcaacd6 | Marc Dequènes (Duck) | ||
ec7296ea | Marc Dequènes (Duck) | def root?
|
|
@parent_node.nil?
|
|||
end
|
|||
eab0c364 | Marc Dequènes (Duck) | def hidden?
|
|
@hidden
|
|||
end
|
|||
ec7296ea | Marc Dequènes (Duck) | def node_path
|
|
return '/' if root?
|
|||
path = @parent_node.node_path
|
|||
path += "/" + @node_name unless @node_name.empty?
|
|||
path.gsub("//", "/")
|
|||
end
|
|||
7466fc08 | Marc Dequènes (Duck) | def add_behavior(&block)
|
|
4761cd13 | Marc Dequènes (Duck) | if block_given?
|
|
@blocks << block
|
|||
else
|
|||
return if @ldir_loaded
|
|||
ec7296ea | Marc Dequènes (Duck) | begin
|
|
Dir.glob(File.join(self.lpath, '*.rb')) do |file|
|
|||
logger.debug "Interface: loading file '#{file}'"
|
|||
begin
|
|||
@blocks << File.read(file)
|
|||
rescue
|
|||
logger.error "Interface: definition file '#{file}' cannot be read"
|
|||
end
|
|||
end
|
|||
rescue
|
|||
logger.error "Interface: directory '#{self.lpath}' cannot be read"
|
|||
4761cd13 | Marc Dequènes (Duck) | end
|
|
@ldir_loaded = true
|
|||
end
|
|||
8fcaacd6 | Marc Dequènes (Duck) | end
|
|
7466fc08 | Marc Dequènes (Duck) | # string, array (useful for aliases), or regex
|
|
# TODO: name validation
|
|||
eab0c364 | Marc Dequènes (Duck) | def node(match, options = {}, &block)
|
|
child_node = self.class.new(@bot, self, options, &block)
|
|||
7466fc08 | Marc Dequènes (Duck) | if match.is_a? Array
|
|
match.each{|n| @nodes[n] = child_node}
|
|||
else
|
|||
@nodes[match] = child_node
|
|||
end
|
|||
8fcaacd6 | Marc Dequènes (Duck) | end
|
|
69a12fdb | Marc Dequènes (Duck) | ||
7bfcd98b | Marc Dequènes (Duck) | def attr_search_node(list = nil, &block)
|
|
list ||= block
|
|||
eab0c364 | Marc Dequènes (Duck) | lookup_node = self
|
|
node '?', :hidden => true do
|
|||
on_request do |request|
|
|||
if request.args.empty?
|
|||
request.reply.results = lookup_node.__send__(:visible_nodes_names)
|
|||
else
|
|||
7bfcd98b | Marc Dequènes (Duck) | request.reply.results = list.nil? ? search_among_children(request, lookup_node) :
|
|
search_in_list(request, lookup_node, list)
|
|||
eab0c364 | Marc Dequènes (Duck) | end
|
|
feb16bd0 | Marc Dequènes (Duck) | request.send_reply
|
|
eab0c364 | Marc Dequènes (Duck) | end
|
|
end
|
|||
end
|
|||
7466fc08 | Marc Dequènes (Duck) | def on_request(&cb)
|
|
@request_cb = cb
|
|||
69a12fdb | Marc Dequènes (Duck) | end
|
|
d1e614b5 | Marc Dequènes (Duck) | ||
7466fc08 | Marc Dequènes (Duck) | def _is_node?(session, node_path)
|
|
node = __send__(:find_node, session, node_path)
|
|||
not node.nil?
|
|||
end
|
|||
69a12fdb | Marc Dequènes (Duck) | ||
feb16bd0 | Marc Dequènes (Duck) | def _call(session, node_path, args = nil, &send_result_cb)
|
|
7466fc08 | Marc Dequènes (Duck) | args ||= []
|
|
7bfcd98b | Marc Dequènes (Duck) | raise CyberError.new(:unrecoverable, 'api/cyborghood', "wrong format for arguments when calling node '#{node_path}'") unless args.is_a? Array
|
|
d1e614b5 | Marc Dequènes (Duck) | ||
7466fc08 | Marc Dequènes (Duck) | node = find_node(session, node_path)
|
|
7bfcd98b | Marc Dequènes (Duck) | raise CyberError.new(:unrecoverable, 'api/cyborghood', "unknown node '#{node_path}'") if node.nil?
|
|
d1e614b5 | Marc Dequènes (Duck) | ||
7466fc08 | Marc Dequènes (Duck) | logger.debug "[Server API] Node '#{node_path}' found"
|
|
feb16bd0 | Marc Dequènes (Duck) | node.__send__(:request, session, args, &send_result_cb)
|
|
7466fc08 | Marc Dequènes (Duck) | end
|
|
23b2305f | Marc Dequènes (Duck) | ||
7466fc08 | Marc Dequènes (Duck) | protected
|
|
23b2305f | Marc Dequènes (Duck) | ||
4761cd13 | Marc Dequènes (Duck) | def base_lpath
|
|
File.join(Config::LIB_DIR, 'cyborghood-' + @bot.name.downcase, 'interface')
|
|||
end
|
|||
def lpath
|
|||
parent_lpath = @parent_node.nil? ? self.base_lpath : @parent_node.lpath
|
|||
@ldir.nil? ? parent_lpath : File.join(parent_lpath, @ldir)
|
|||
end
|
|||
7466fc08 | Marc Dequènes (Duck) | def load(node_element = '')
|
|
cleanup
|
|||
@node_name = node_element
|
|||
@blocks.each do |bl|
|
|||
ec7296ea | Marc Dequènes (Duck) | begin
|
|
if bl.is_a? String
|
|||
instance_eval bl
|
|||
else
|
|||
instance_eval &bl
|
|||
end
|
|||
rescue Exception => e
|
|||
logger.error "Interface: a definition block for node '#{node_path}' is buggy: " + e.message
|
|||
4761cd13 | Marc Dequènes (Duck) | end
|
|
7466fc08 | Marc Dequènes (Duck) | end
|
|
end
|
|||
1c89625f | Marc Dequènes (Duck) | ||
7466fc08 | Marc Dequènes (Duck) | def find_node(session, node_path)
|
|
4761cd13 | Marc Dequènes (Duck) | # node_path is a string argument when interface root node is called, but is a list of node elements later on
|
|
7466fc08 | Marc Dequènes (Duck) | if root?
|
|
logger.debug "[Server API] Looking for node '#{node_path}'"
|
|||
node_path = node_path.split("/")
|
|||
# remove empty string before first "/"
|
|||
node_path.shift
|
|||
# initial load
|
|||
load
|
|||
end
|
|||
41802ec1 | Marc Dequènes (Duck) | ||
7466fc08 | Marc Dequènes (Duck) | node_element = node_path.shift
|
|
logger.debug "[Server API] Looking for node element '#{node_element}'"
|
|||
if node_element.nil?
|
|||
return self
|
|||
41802ec1 | Marc Dequènes (Duck) | else
|
|
7466fc08 | Marc Dequènes (Duck) | next_node = find_child_node(node_element)
|
|
if next_node
|
|||
next_node.__send__(:load, node_element)
|
|||
return next_node.__send__(:find_node, session, node_path)
|
|||
else
|
|||
return
|
|||
end
|
|||
41802ec1 | Marc Dequènes (Duck) | end
|
|
end
|
|||
7466fc08 | Marc Dequènes (Duck) | def find_child_node(child_node)
|
|
return @nodes[child_node] if @nodes.has_key? child_node
|
|||
@nodes.each_pair do |match, node|
|
|||
found = if match.is_a? String
|
|||
child_node == match
|
|||
elsif match.is_a? Regexp
|
|||
child_node =~ Regexp.new(match)
|
|||
elsif match.is_a? Proc
|
|||
match.call.include? child_node
|
|||
end
|
|||
return node if found
|
|||
end
|
|||
f94d64b1 | Marc Dequènes (Duck) | ||
7466fc08 | Marc Dequènes (Duck) | nil
|
|
d1e614b5 | Marc Dequènes (Duck) | end
|
|
7466fc08 | Marc Dequènes (Duck) | class Request
|
|
attr_reader :session, :args, :reply
|
|||
b7f7d214 | Marc Dequènes (Duck) | ||
feb16bd0 | Marc Dequènes (Duck) | def initialize(session, args = [], &send_result_cb)
|
|
7466fc08 | Marc Dequènes (Duck) | @session = session
|
|
@args = args
|
|||
feb16bd0 | Marc Dequènes (Duck) | @send_result_cb = send_result_cb
|
|
b7f7d214 | Marc Dequènes (Duck) | ||
7466fc08 | Marc Dequènes (Duck) | @reply = {
|
|
:results => {},
|
|||
:infos => [],
|
|||
:warnings => [],
|
|||
:errors => []
|
|||
}.to_ostruct
|
|||
b7f7d214 | Marc Dequènes (Duck) | end
|
|
feb16bd0 | Marc Dequènes (Duck) | ||
def send_reply
|
|||
@send_result_cb.call @reply.to_hash
|
|||
end
|
|||
b7f7d214 | Marc Dequènes (Duck) | end
|
|
feb16bd0 | Marc Dequènes (Duck) | def request(session, args = [], &send_result_cb)
|
|
request = Request.new(session, args, &send_result_cb)
|
|||
7466fc08 | Marc Dequènes (Duck) | if @request_cb
|
|
begin
|
|||
@request_cb.call(request)
|
|||
rescue
|
|||
logger.debug "node request error message: " + $!
|
|||
logger.debug "node request error backtrace: " + $!.backtrace.join("\n")
|
|||
7bfcd98b | Marc Dequènes (Duck) | raise CyberError.new(:unrecoverable, 'api/cyborghood', "call failed on node '#{node_path}': " + $!)
|
|
b7f7d214 | Marc Dequènes (Duck) | end
|
|
7466fc08 | Marc Dequènes (Duck) | else
|
|
feb16bd0 | Marc Dequènes (Duck) | request.reply.results = visible_nodes_names
|
|
request.send_reply
|
|||
eab0c364 | Marc Dequènes (Duck) | end
|
|
end
|
|||
def visible_nodes
|
|||
Hash[@nodes.select{|match, node| not node.hidden? }]
|
|||
end
|
|||
def node_match_to_name(match)
|
|||
if match.is_a? String
|
|||
match
|
|||
elsif match.is_a? Regexp
|
|||
'/' + match.to_s + '/'
|
|||
elsif match.is_a? Proc
|
|||
match.call
|
|||
b7f7d214 | Marc Dequènes (Duck) | end
|
|
end
|
|||
d1e614b5 | Marc Dequènes (Duck) | ||
eab0c364 | Marc Dequènes (Duck) | def visible_nodes_names
|
|
visible_nodes.keys.collect do |match|
|
|||
node_match_to_name(match)
|
|||
end.compact.flatten
|
|||
end
|
|||
7466fc08 | Marc Dequènes (Duck) | def cleanup
|
|
@nodes = {}
|
|||
@request_cb = nil
|
|||
4761cd13 | Marc Dequènes (Duck) | ||
# data memorized during walk in the node tree
|
|||
if root?
|
|||
@store = OpenStruct.new
|
|||
else
|
|||
@store = @parent_node.store
|
|||
end
|
|||
a3483167 | Marc Dequènes (Duck) | end
|
|
eab0c364 | Marc Dequènes (Duck) | ||
def hash_match_criterias(hash, crit)
|
|||
crit.each do |key, wanted_value|
|
|||
2c35cfa1 | Marc Dequènes (Duck) | value = hash[key]
|
|
eab0c364 | Marc Dequènes (Duck) | ||
if wanted_value.is_a? Array
|
|||
return false unless wanted_value.include?(value)
|
|||
elsif wanted_value.is_a? Regexp
|
|||
return false unless value =~ wanted_value
|
|||
else
|
|||
return false unless value == wanted_value
|
|||
end
|
|||
end
|
|||
true
|
|||
end
|
|||
7bfcd98b | Marc Dequènes (Duck) | ||
def search_among_children(request, lookup_node)
|
|||
ee2c32c3 | Marc Dequènes (Duck) | criterias = request.args.first
|
|
return {} unless criterias.is_a? Hash
|
|||
node_names_list = {}
|
|||
7bfcd98b | Marc Dequènes (Duck) | ||
lookup_node.__send__(:visible_nodes).each do |match, node|
|
|||
if match.is_a? String
|
|||
match_list = [match]
|
|||
elsif match.is_a? Proc
|
|||
match_list = match.call
|
|||
else
|
|||
next
|
|||
end
|
|||
match_list.each do |child_node_name|
|
|||
node.__send__(:load, child_node_name)
|
|||
result = node.__send__(:request, request.session)
|
|||
next unless result.respond_to? :to_hash
|
|||
ee2c32c3 | Marc Dequènes (Duck) | child_node_attrs = result.to_hash
|
|
if hash_match_criterias(child_node_attrs, criterias)
|
|||
node_names_list[child_node_name] = child_node_attrs
|
|||
7bfcd98b | Marc Dequènes (Duck) | end
|
|
end
|
|||
end
|
|||
node_names_list
|
|||
end
|
|||
def search_in_list(request, lookup_node, list)
|
|||
criterias = request.args.first
|
|||
ee2c32c3 | Marc Dequènes (Duck) | return {} unless criterias.is_a? Hash
|
|
7bfcd98b | Marc Dequènes (Duck) | ||
if list.is_a? Proc
|
|||
ee2c32c3 | Marc Dequènes (Duck) | data = list.call(criterias)
|
|
return data[:list] unless data[:post_filter]
|
|||
7bfcd98b | Marc Dequènes (Duck) | end
|
|
ee2c32c3 | Marc Dequènes (Duck) | node_names_list = {}
|
|
data[:list].each do |element_name, element|
|
|||
element_attrs = element.to_hash
|
|||
7bfcd98b | Marc Dequènes (Duck) | ||
ee2c32c3 | Marc Dequènes (Duck) | if hash_match_criterias(element_attrs, criterias)
|
|
node_names_list[element_name] = element_attrs
|
|||
7bfcd98b | Marc Dequènes (Duck) | end
|
|
end
|
|||
node_names_list
|
|||
end
|
|||
a3483167 | Marc Dequènes (Duck) | end
|
|
7bfcd98b | Marc Dequènes (Duck) | end # DSL
|
|
d1e614b5 | Marc Dequènes (Duck) | end
|