mcollective-2.0.0/0000755000175000017500000000000011747546503013041 5ustar jonasjonasmcollective-2.0.0/mcollective.init0000755000175000017500000000554311747546503016246 0ustar jonasjonas#!/bin/sh # # mcollective Application Server for STOMP based agents # # chkconfig: 345 24 76 # # description: mcollective lets you build powerful Stomp compatible middleware clients in ruby without having to worry too # much about all the setup and management of a Stomp connection, it also provides stats, logging and so forth # as a bonus. # ### BEGIN INIT INFO # Provides: mcollective # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start daemon at boot time # Description: Enable service provided by daemon. ### END INIT INFO mcollectived="/usr/sbin/mcollectived" # Lockfile if [ -d /var/lock/subsys ]; then # RedHat/CentOS/etc who use subsys lock="/var/lock/subsys/mcollective" else # The rest of them lock="/var/lock/mcollective" fi # PID directory pidfile="/var/run/mcollectived.pid" # Source function library. . /lib/lsb/init-functions # Check that binary exists if ! [ -f $mcollectived ] then echo "mcollectived binary not found" exit 0 fi # See how we were called. case "$1" in start) echo -n "Starting mcollective: " if [ -f ${lock} ]; then # we were not shut down correctly if [ -s ${pidfile} ]; then kill `cat ${pidfile}` >/dev/null 2>&1 fi rm -f ${pidfile} rm -f ${lock} sleep 2 fi rm -f ${pidfile} ${mcollectived} --pid=${pidfile} --config="/etc/mcollective/server.cfg" if [ $? = 0 ]; then log_success_msg touch $lock exit 0 else log_failure_msg exit 1 fi ;; stop) echo -n "Shutting down mcollective: " if [ -s ${pidfile} ]; then kill `cat ${pidfile}` >/dev/null 2>&1 fi rm -f ${pidfile} log_success_msg rm -f $lock ;; restart) $0 stop sleep 2 $0 start ;; condrestart) if [ -f $lock ]; then $0 stop # avoid race sleep 2 $0 start fi ;; status) if [ -f ${lock} ]; then if [ -s ${pidfile} ]; then if [ -e /proc/`cat ${pidfile}` ]; then echo "mcollectived (`cat ${pidfile}`) is running" exit 0 else echo "mcollectived (`cat ${pidfile}`) is NOT running" exit 1 fi fi else echo "mcollectived: service not started" exit 1 fi ;; force-reload) echo "not implemented" ;; *) echo "Usage: mcollectived {start|stop|restart|condrestart|status}" exit 1 ;; esac exit 0 mcollective-2.0.0/etc/0000755000175000017500000000000011747546503013614 5ustar jonasjonasmcollective-2.0.0/etc/rpc-help.erb0000644000175000017500000000266111747546503016025 0ustar jonasjonas<%= meta[:name] %> <% meta[:name].size.times do %>=<% end %> <%= meta[:description] %> Author: <%= meta[:author] %> Version: <%= meta[:version] %> License: <%= meta[:license] %> Timeout: <%= meta[:timeout] %> Home Page: <%= meta[:url] %> ACTIONS: ======== <%= actions.keys.sort.join(", ") %> % actions.keys.sort.each do |action| <%= action %> action: <% (action.size + 8).times do %>-<% end %> <%= actions[action][:description] %> INPUT: % actions[action][:input].keys.sort.each do |input| <%= input %>: Description: <%= actions[action][:input][input][:description] %> Prompt: <%= actions[action][:input][input][:prompt] %> Type: <%= actions[action][:input][input][:type] %> % if actions[action][:input][input][:type] == :string Validation: <%= actions[action][:input][input][:validation] %> Length: <%= actions[action][:input][input][:maxlength] %> % elsif actions[action][:input][input][:type] == :list Valid Values: <%= actions[action][:input][input][:list].join(", ") %> % end % end OUTPUT: % actions[action][:output].keys.sort.each do |output| <%= output %>: Description: <%= actions[action][:output][output][:description] %> Display As: <%= actions[action][:output][output][:display_as] %> % end % end mcollective-2.0.0/etc/server.cfg.dist0000644000175000017500000000066611747546503016555 0ustar jonasjonastopicprefix = /topic/ main_collective = mcollective collectives = mcollective libdir = /usr/libexec/mcollective logfile = /var/log/mcollective.log loglevel = info daemonize = 1 # Plugins securityprovider = psk plugin.psk = unset connector = stomp plugin.stomp.host = localhost plugin.stomp.port = 61613 plugin.stomp.user = mcollective plugin.stomp.password = secret # Facts factsource = yaml plugin.yaml = /etc/mcollective/facts.yaml mcollective-2.0.0/etc/client.cfg.dist0000644000175000017500000000063311747546503016517 0ustar jonasjonastopicprefix = /topic/ main_collective = mcollective collectives = mcollective libdir = /usr/libexec/mcollective logger_type = console loglevel = warn # Plugins securityprovider = psk plugin.psk = unset connector = stomp plugin.stomp.host = localhost plugin.stomp.port = 61613 plugin.stomp.user = mcollective plugin.stomp.password = secret # Facts factsource = yaml plugin.yaml = /etc/mcollective/facts.yaml mcollective-2.0.0/etc/facts.yaml.dist0000644000175000017500000000002311747546503016535 0ustar jonasjonas--- mcollective: 1 mcollective-2.0.0/etc/ssl/0000755000175000017500000000000011747546503014415 5ustar jonasjonasmcollective-2.0.0/etc/ssl/PLACEHOLDER0000644000175000017500000000000011747546503016050 0ustar jonasjonasmcollective-2.0.0/etc/ssl/clients/0000755000175000017500000000000011747546503016056 5ustar jonasjonasmcollective-2.0.0/etc/ssl/clients/PLACEHOLDER0000644000175000017500000000000011747546503017511 0ustar jonasjonasmcollective-2.0.0/doc/0000755000175000017500000000000011747546503013606 5ustar jonasjonasmcollective-2.0.0/doc/index.html0000644000175000017500000000142711747546503015607 0ustar jonasjonas The Marionette Collective version 2.0.0 mcollective-2.0.0/doc/fr_file_index.html0000644000175000017500000001234411747546503017275 0ustar jonasjonas Files

Files

lib/mcollective.rb
lib/mcollective/agents.rb
lib/mcollective/application.rb
lib/mcollective/applications.rb
lib/mcollective/client.rb
lib/mcollective/config.rb
lib/mcollective/connector.rb
lib/mcollective/connector/base.rb
lib/mcollective/facts.rb
lib/mcollective/facts/base.rb
lib/mcollective/log.rb
lib/mcollective/logger.rb
lib/mcollective/logger/base.rb
lib/mcollective/logger/console_logger.rb
lib/mcollective/logger/file_logger.rb
lib/mcollective/logger/syslog_logger.rb
lib/mcollective/matcher.rb
lib/mcollective/matcher/parser.rb
lib/mcollective/matcher/scanner.rb
lib/mcollective/message.rb
lib/mcollective/monkey_patches.rb
lib/mcollective/optionparser.rb
lib/mcollective/pluginmanager.rb
lib/mcollective/pluginpackager.rb
lib/mcollective/pluginpackager/agent_definition.rb
lib/mcollective/pluginpackager/standard_definition.rb
lib/mcollective/registration.rb
lib/mcollective/registration/base.rb
lib/mcollective/rpc.rb
lib/mcollective/rpc/actionrunner.rb
lib/mcollective/rpc/agent.rb
lib/mcollective/rpc/audit.rb
lib/mcollective/rpc/client.rb
lib/mcollective/rpc/ddl.rb
lib/mcollective/rpc/helpers.rb
lib/mcollective/rpc/progress.rb
lib/mcollective/rpc/reply.rb
lib/mcollective/rpc/request.rb
lib/mcollective/rpc/result.rb
lib/mcollective/rpc/stats.rb
lib/mcollective/runner.rb
lib/mcollective/runnerstats.rb
lib/mcollective/security.rb
lib/mcollective/security/base.rb
lib/mcollective/shell.rb
lib/mcollective/ssl.rb
lib/mcollective/unix_daemon.rb
lib/mcollective/util.rb
lib/mcollective/windows_daemon.rb
mcollective-2.0.0/doc/fr_method_index.html0000644000175000017500000010450711747546503017641 0ustar jonasjonas Methods

Methods

<< (MCollective::PluginManager)
<=> (Symbol)
[] (MCollective::PluginManager)
[] (MCollective::Facts)
[] (MCollective::RPC::Request)
[] (MCollective::Applications)
[] (MCollective::PluginPackager)
[] (MCollective::RPC::Result)
[] (MCollective::RPC::Reply)
[] (MCollective::Application)
[] (MCollective::RPC::Stats)
[]= (MCollective::RPC::Result)
[]= (MCollective::Application)
[]= (MCollective::RPC::Reply)
action (MCollective::RPC::DDL)
action_interface (MCollective::RPC::DDL)
actions (MCollective::RPC::DDL)
actions (MCollective::RPC::Agent)
activate? (MCollective::RPC::Agent)
activate_agent? (MCollective::Agents)
add_common_options (MCollective::Optionparser)
add_filter_options (MCollective::Optionparser)
add_required_options (MCollective::Optionparser)
add_simplerpc_options (MCollective::RPC::Helpers)
aes_decrypt (MCollective::SSL)
aes_encrypt (MCollective::SSL)
agent (MCollective::PluginPackager::AgentDefinition)
agent_filter (MCollective::RPC::Client)
agentlist (MCollective::Agents)
application_cli_arguments (MCollective::Application)
application_description (MCollective::Application)
application_failure (MCollective::Application)
application_options (MCollective::Application)
application_options (MCollective::Application)
application_parse_options (MCollective::Application)
application_usage (MCollective::Application)
audit_request (MCollective::RPC::Audit)
base64? (MCollective::Message)
base64_decode (MCollective::SSL)
base64_decode (MCollective::SSL)
base64_decode! (MCollective::Message)
base64_encode (MCollective::SSL)
base64_encode (MCollective::SSL)
base64_encode! (MCollective::Message)
batch_size= (MCollective::RPC::Client)
batch_sleep_time= (MCollective::RPC::Client)
build_tool? (MCollective::PluginPackager)
callerid (MCollective::Security::Base)
canrun? (MCollective::RPC::ActionRunner)
check_dir_present (MCollective::PluginPackager)
class_filter (MCollective::RPC::Client)
class_for_agent (MCollective::Agents)
clear (MCollective::PluginManager)
clear! (MCollective::Agents)
client (MCollective::PluginPackager::AgentDefinition)
client_stats= (MCollective::RPC::Stats)
clioptions (MCollective::Application)
collective (MCollective::Client)
collective= (MCollective::RPC::Client)
color (MCollective::Logger::Console_logger)
color (MCollective::RPC::Helpers)
colorize (MCollective::Logger::Console_logger)
colorize (MCollective::RPC::Helpers)
command_in_path? (MCollective::RPC::Helpers)
common (MCollective::PluginPackager::StandardDefinition)
common (MCollective::PluginPackager::AgentDefinition)
compound_filter (MCollective::RPC::Client)
config (MCollective::Registration::Base)
config_file_for_user (MCollective::Util)
configuration (MCollective::Application)
configure (MCollective::Log)
create_instance (MCollective::PluginManager)
create_reply (MCollective::Security::Base)
create_reqid (MCollective::Message)
create_request (MCollective::Security::Base)
custom_request (MCollective::RPC::Client)
cycle_level (MCollective::Logger::Base)
cycle_level (MCollective::Log)
daemonize (MCollective::UnixDaemon)
daemonize_runner (MCollective::WindowsDaemon)
daemonize_runner (MCollective::UnixDaemon)
debug (MCollective::Log)
decode! (MCollective::Message)
decodemsg (MCollective::Security::Base)
decrypt_with_private (MCollective::SSL)
decrypt_with_public (MCollective::SSL)
default_options (MCollective::Util)
delete (MCollective::PluginManager)
description (MCollective::Application)
disconnect (MCollective::Client)
disconnect (MCollective::Application)
disconnect (MCollective::RPC::Client)
discover (MCollective::Client)
discover (MCollective::RPC::Client)
discovered (MCollective::RPC)
discovered_agents (MCollective::RPC::Stats)
discovered_req (MCollective::Client)
dispatch (MCollective::Agents)
display (MCollective::RPC::DDL)
display_stats (MCollective::Client)
do_quietly? (MCollective::PluginPackager)
each (MCollective::RPC::Result)
empty_filter (MCollective::Util)
empty_filter? (MCollective::RPC)
empty_filter? (MCollective::Util)
encode! (MCollective::Message)
encodereply (MCollective::Security::Base)
encoderequest (MCollective::Security::Base)
encrypt_with_private (MCollective::SSL)
encrypt_with_public (MCollective::SSL)
error (MCollective::Log)
eval_compound_statement (MCollective::Util)
exclude_argument_sections (MCollective::Application)
expected_msgid= (MCollective::Message)
extract_hosts_from_array (MCollective::RPC::Helpers)
extract_hosts_from_json (MCollective::RPC::Helpers)
fact_filter (MCollective::RPC::Client)
fail (MCollective::RPC::Stats)
fail (MCollective::RPC::Reply)
fail! (MCollective::RPC::Reply)
fatal (MCollective::Log)
filter_extra_options (MCollective::Applications)
filtered (MCollective::RunnerStats)
find (MCollective::PluginManager)
find_and_load (MCollective::PluginManager)
findagentfile (MCollective::Agents)
findddlfile (MCollective::RPC::DDL)
finish_request (MCollective::RPC::Stats)
force_reload? (MCollective::Facts::Base)
from (MCollective::Log)
get_fact (MCollective::Util)
get_fact (MCollective::Facts::Base)
get_fact (MCollective::Facts)
get_facts (MCollective::Facts::Base)
get_metadata (MCollective::PluginPackager)
get_token (MCollective::Matcher::Scanner)
grep (MCollective::PluginManager)
halt (MCollective::Application)
handlemsg (MCollective::RPC::Agent)
has_agent? (MCollective::Util)
has_cf_class? (MCollective::Util)
has_fact? (MCollective::Facts::Base)
has_fact? (MCollective::Facts)
has_fact? (MCollective::Util)
has_identity? (MCollective::Util)
help (MCollective::RPC::DDL)
help (MCollective::Agents)
help (MCollective::RPC::Agent)
help (MCollective::Application)
help (MCollective::RPC::Client)
help (MCollective::RPC::Agent)
identify_packages (MCollective::PluginPackager::StandardDefinition)
identify_packages (MCollective::PluginPackager::AgentDefinition)
identity_filter (MCollective::RPC::Client)
in_groups_of (Array)
include? (MCollective::PluginManager)
include? (MCollective::Agents)
include? (MCollective::RPC::Request)
info (MCollective::Log)
inherited (MCollective::Registration::Base)
inherited (MCollective::Connector::Base)
inherited (MCollective::Facts::Base)
inherited (MCollective::Security::Base)
inherited (MCollective::RPC::Audit)
input (MCollective::RPC::DDL)
instance (MCollective::Log)
interval (MCollective::Registration::Base)
intialize_application_options (MCollective::Application)
limit_method= (MCollective::RPC::Client)
limit_targets= (MCollective::RPC::Client)
list (MCollective::Applications)
load_application (MCollective::Applications)
load_config (MCollective::Applications)
load_json_results (MCollective::RPC::ActionRunner)
load_packagers (MCollective::PluginPackager)
load_results (MCollective::RPC::ActionRunner)
loadagent (MCollective::Agents)
loadagents (MCollective::Agents)
loadclass (MCollective::Util)
loadclass (MCollective::PluginManager)
loadconfig (MCollective::Config)
log (MCollective::Logger::File_logger)
log (MCollective::Logger::Console_logger)
log (MCollective::Log)
log (MCollective::Logger::Syslog_logger)
logger (MCollective::Log)
main (MCollective::Application)
make_subscriptions (MCollective::Util)
md5 (MCollective::SSL)
md5 (MCollective::SSL)
metadata (MCollective::RPC::DDL)
method_missing (MCollective::RPC::Client)
mktmpdir (Dir)
msg_filter (MCollective::Registration::Base)
new (MCollective::RPC::ActionRunner)
new (MCollective::Client)
new (MCollective::RPC::Stats)
new (MCollective::RunnerStats)
new (MCollective::Logger::Base)
new (MCollective::PluginPackager::AgentDefinition)
new (MCollective::PluginPackager::StandardDefinition)
new (MCollective::Optionparser)
new (MCollective::Matcher::Parser)
new (MCollective::RPC::Agent)
new (MCollective::Facts::Base)
new (MCollective::Runner)
new (MCollective::Matcher::Scanner)
new (MCollective::Config)
new (MCollective::Message)
new (MCollective::Agents)
new (MCollective::SSL)
new (MCollective::RPC::DDL)
new (MCollective::RPC::Result)
new (MCollective::RPC::Reply)
new (MCollective::Shell)
new (MCollective::RPC::Request)
new (MCollective::RPC::Client)
new (MCollective::RPC::Progress)
new (MCollective::Security::Base)
new_request (MCollective::RPC::Client)
no_response_report (MCollective::RPC::Stats)
node_responded (MCollective::RPC::Stats)
ok (MCollective::RPC::Stats)
old_rpcresults (MCollective::RPC::Helpers)
option (MCollective::Application)
options (MCollective::Application)
options (MCollective::RPC::Client)
output (MCollective::RPC::DDL)
parse (MCollective::Optionparser)
parse (MCollective::Matcher::Parser)
parse_fact_string (MCollective::Util)
passed (MCollective::RunnerStats)
path_to_command (MCollective::RPC::ActionRunner)
plugin (MCollective::PluginPackager::StandardDefinition)
pluginlist (MCollective::PluginManager)
printrpc (MCollective::RPC)
printrpcstats (MCollective::RPC)
publish (MCollective::Registration::Base)
publish (MCollective::Message)
read_key (MCollective::SSL)
read_plugin_config_dir (MCollective::Config)
receive (MCollective::Client)
received (MCollective::RunnerStats)
reply (MCollective::RPC)
reply_to= (MCollective::Message)
report (MCollective::RPC::Stats)
req (MCollective::Client)
request (MCollective::RPC)
reset (MCollective::RPC::Stats)
reset (MCollective::RPC::Client)
reset_filter (MCollective::RPC::Client)
rpcclient (MCollective::Application)
rpcclient (MCollective::RPC)
rpcoptions (MCollective::RPC)
rpcresults (MCollective::RPC::Helpers)
rsa_decrypt_with_private (MCollective::SSL)
rsa_decrypt_with_public (MCollective::SSL)
rsa_encrypt_with_private (MCollective::SSL)
rsa_encrypt_with_public (MCollective::SSL)
ruby_version (MCollective::Util)
run (MCollective::Registration::Base)
run (MCollective::Application)
run (MCollective::RPC::ActionRunner)
run (MCollective::Applications)
run (MCollective::Runner)
runcommand (MCollective::Shell)
safe_system (MCollective::PluginPackager)
save_json_request (MCollective::RPC::ActionRunner)
saverequest (MCollective::RPC::ActionRunner)
sendreq (MCollective::Client)
sent (MCollective::RunnerStats)
service_main (MCollective::WindowsDaemon)
service_stop (MCollective::WindowsDaemon)
set_config_defaults (MCollective::Config)
set_level (MCollective::Logger::Base)
set_logger (MCollective::Log)
set_logging_level (MCollective::Logger::Console_logger)
set_logging_level (MCollective::Logger::Syslog_logger)
set_logging_level (MCollective::Logger::File_logger)
setup_windows_sleeper (MCollective::Util)
shell (MCollective::RPC::ActionRunner)
shellescape (MCollective::Util)
should_process_msg? (MCollective::Security::Base)
should_respond? (MCollective::RPC::Request)
sign (MCollective::SSL)
start (MCollective::Logger::File_logger)
start (MCollective::Logger::Syslog_logger)
start (MCollective::Logger::Console_logger)
stats (MCollective::RPC)
subscribe (MCollective::Client)
subscribe (MCollective::Util)
syslog_facility (MCollective::Logger::Syslog_logger)
target_collective (MCollective::Registration::Base)
tempfile (MCollective::RPC::ActionRunner)
terminal_dimensions (MCollective::RPC::Helpers)
text_for_flattened_result (MCollective::RPC::Helpers)
text_for_result (MCollective::RPC::Helpers)
time_block_execution (MCollective::RPC::Stats)
time_discovery (MCollective::RPC::Stats)
tmpdir (Dir)
to_hash (MCollective::RPC::Reply)
to_hash (MCollective::RunnerStats)
to_hash (MCollective::RPC::Request)
to_hash (MCollective::RPC::Stats)
to_json (MCollective::RPC::Result)
to_json (MCollective::RPC::Request)
to_s (MCollective::RPC::ActionRunner)
ttlexpired (MCollective::RunnerStats)
twirl (MCollective::RPC::Progress)
type= (MCollective::Message)
unsubscribe (MCollective::Util)
unsubscribe (MCollective::Client)
unvalidated (MCollective::RunnerStats)
usage (MCollective::Application)
valid_callerid? (MCollective::Security::Base)
valid_levels (MCollective::Logger::File_logger)
valid_levels (MCollective::Logger::Console_logger)
valid_levels (MCollective::Logger::Syslog_logger)
validate (MCollective::Message)
validate_cli_options (MCollective::Application)
validate_filter? (MCollective::Security::Base)
validate_option (MCollective::Application)
validate_request (MCollective::RPC::DDL)
validated (MCollective::RunnerStats)
validrequest? (MCollective::Security::Base)
verify_signature (MCollective::SSL)
version (MCollective)
warn (MCollective::Log)
windows? (MCollective::Util)
mcollective-2.0.0/doc/files/0000755000175000017500000000000011747546503014710 5ustar jonasjonasmcollective-2.0.0/doc/files/lib/0000755000175000017500000000000011747546503015456 5ustar jonasjonasmcollective-2.0.0/doc/files/lib/mcollective/0000755000175000017500000000000011747546503017764 5ustar jonasjonasmcollective-2.0.0/doc/files/lib/mcollective/util_rb.html0000644000175000017500000000371711747546503022322 0ustar jonasjonas File: util.rb

util.rb

Path: lib/mcollective/util.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/rpc_rb.html0000644000175000017500000000414711747546503022127 0ustar jonasjonas File: rpc.rb

rpc.rb

Path: lib/mcollective/rpc.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

Required files

pp  

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/application_rb.html0000644000175000017500000000374411747546503023650 0ustar jonasjonas File: application.rb

application.rb

Path: lib/mcollective/application.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/ssl_rb.html0000644000175000017500000000424311747546503022141 0ustar jonasjonas File: ssl.rb

ssl.rb

Path: lib/mcollective/ssl.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

Required files

openssl   base64   digest/sha1  

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/security_rb.html0000644000175000017500000000373311747546503023212 0ustar jonasjonas File: security.rb

security.rb

Path: lib/mcollective/security.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/security/0000755000175000017500000000000011747546503021633 5ustar jonasjonasmcollective-2.0.0/doc/files/lib/mcollective/security/base_rb.html0000644000175000017500000000373311747546503024124 0ustar jonasjonas File: base.rb

base.rb

Path: lib/mcollective/security/base.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/registration/0000755000175000017500000000000011747546503022476 5ustar jonasjonasmcollective-2.0.0/doc/files/lib/mcollective/registration/base_rb.html0000644000175000017500000000373711747546503024773 0ustar jonasjonas File: base.rb

base.rb

Path: lib/mcollective/registration/base.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/pluginpackager/0000755000175000017500000000000011747546503022760 5ustar jonasjonasmcollective-2.0.0/doc/files/lib/mcollective/pluginpackager/standard_definition_rb.html0000644000175000017500000000401611747546503030342 0ustar jonasjonas File: standard_definition.rb

standard_definition.rb

Path: lib/mcollective/pluginpackager/standard_definition.rb
Last Update: Tue Apr 24 23:03:29 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/pluginpackager/agent_definition_rb.html0000644000175000017500000000400511747546503027636 0ustar jonasjonas File: agent_definition.rb

agent_definition.rb

Path: lib/mcollective/pluginpackager/agent_definition.rb
Last Update: Tue Apr 24 23:03:29 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/facts/0000755000175000017500000000000011747546503021064 5ustar jonasjonasmcollective-2.0.0/doc/files/lib/mcollective/facts/base_rb.html0000644000175000017500000000373011747546503023352 0ustar jonasjonas File: base.rb

base.rb

Path: lib/mcollective/facts/base.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/registration_rb.html0000644000175000017500000000374711747546503024062 0ustar jonasjonas File: registration.rb

registration.rb

Path: lib/mcollective/registration.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/client_rb.html0000644000175000017500000000372511747546503022622 0ustar jonasjonas File: client.rb

client.rb

Path: lib/mcollective/client.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/rpc/0000755000175000017500000000000011747546503020550 5ustar jonasjonasmcollective-2.0.0/doc/files/lib/mcollective/rpc/agent_rb.html0000644000175000017500000000422111747546503023216 0ustar jonasjonas File: agent.rb

agent.rb

Path: lib/mcollective/rpc/agent.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

Required files

ipaddr   ipaddr  

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/rpc/actionrunner_rb.html0000644000175000017500000000375611747546503024643 0ustar jonasjonas File: actionrunner.rb

actionrunner.rb

Path: lib/mcollective/rpc/actionrunner.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/rpc/audit_rb.html0000644000175000017500000000373111747546503023233 0ustar jonasjonas File: audit.rb

audit.rb

Path: lib/mcollective/rpc/audit.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/rpc/progress_rb.html0000644000175000017500000000374211747546503023773 0ustar jonasjonas File: progress.rb

progress.rb

Path: lib/mcollective/rpc/progress.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/rpc/helpers_rb.html0000644000175000017500000000373711747546503023575 0ustar jonasjonas File: helpers.rb

helpers.rb

Path: lib/mcollective/rpc/helpers.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/rpc/stats_rb.html0000644000175000017500000000373111747546503023263 0ustar jonasjonas File: stats.rb

stats.rb

Path: lib/mcollective/rpc/stats.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/rpc/client_rb.html0000644000175000017500000000373411747546503023406 0ustar jonasjonas File: client.rb

client.rb

Path: lib/mcollective/rpc/client.rb
Last Update: Mon Apr 30 17:34:07 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/rpc/request_rb.html0000644000175000017500000000373711747546503023623 0ustar jonasjonas File: request.rb

request.rb

Path: lib/mcollective/rpc/request.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/rpc/ddl_rb.html0000644000175000017500000000372311747546503022671 0ustar jonasjonas File: ddl.rb

ddl.rb

Path: lib/mcollective/rpc/ddl.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/rpc/reply_rb.html0000644000175000017500000000373111747546503023260 0ustar jonasjonas File: reply.rb

reply.rb

Path: lib/mcollective/rpc/reply.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/rpc/result_rb.html0000644000175000017500000000373411747546503023446 0ustar jonasjonas File: result.rb

result.rb

Path: lib/mcollective/rpc/result.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/matcher/0000755000175000017500000000000011747546503021407 5ustar jonasjonasmcollective-2.0.0/doc/files/lib/mcollective/matcher/scanner_rb.html0000644000175000017500000000374311747546503024420 0ustar jonasjonas File: scanner.rb

scanner.rb

Path: lib/mcollective/matcher/scanner.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/matcher/parser_rb.html0000644000175000017500000000374011747546503024260 0ustar jonasjonas File: parser.rb

parser.rb

Path: lib/mcollective/matcher/parser.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/shell_rb.html0000644000175000017500000000372211747546503022450 0ustar jonasjonas File: shell.rb

shell.rb

Path: lib/mcollective/shell.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/log_rb.html0000644000175000017500000000371411747546503022123 0ustar jonasjonas File: log.rb

log.rb

Path: lib/mcollective/log.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/connector/0000755000175000017500000000000011747546503021756 5ustar jonasjonasmcollective-2.0.0/doc/files/lib/mcollective/connector/base_rb.html0000644000175000017500000000373411747546503024250 0ustar jonasjonas File: base.rb

base.rb

Path: lib/mcollective/connector/base.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/runner_rb.html0000644000175000017500000000372511747546503022655 0ustar jonasjonas File: runner.rb

runner.rb

Path: lib/mcollective/runner.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/unix_daemon_rb.html0000644000175000017500000000374411747546503023653 0ustar jonasjonas File: unix_daemon.rb

unix_daemon.rb

Path: lib/mcollective/unix_daemon.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/pluginpackager_rb.html0000644000175000017500000000375511747546503024343 0ustar jonasjonas File: pluginpackager.rb

pluginpackager.rb

Path: lib/mcollective/pluginpackager.rb
Last Update: Tue Apr 24 23:03:29 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/connector_rb.html0000644000175000017500000000373611747546503023340 0ustar jonasjonas File: connector.rb

connector.rb

Path: lib/mcollective/connector.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/applications_rb.html0000644000175000017500000000374711747546503024036 0ustar jonasjonas File: applications.rb

applications.rb

Path: lib/mcollective/applications.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/message_rb.html0000644000175000017500000000373011747546503022764 0ustar jonasjonas File: message.rb

message.rb

Path: lib/mcollective/message.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/monkey_patches_rb.html0000644000175000017500000000467311747546503024360 0ustar jonasjonas File: monkey_patches.rb

monkey_patches.rb

Path: lib/mcollective/monkey_patches.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

Make arrays of Symbols sortable

Constants

RbConfig = ::Config

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/config_rb.html0000644000175000017500000000372511747546503022611 0ustar jonasjonas File: config.rb

config.rb

Path: lib/mcollective/config.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/agents_rb.html0000644000175000017500000000372511747546503022625 0ustar jonasjonas File: agents.rb

agents.rb

Path: lib/mcollective/agents.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/windows_daemon_rb.html0000644000175000017500000000422211747546503024352 0ustar jonasjonas File: windows_daemon.rb

windows_daemon.rb

Path: lib/mcollective/windows_daemon.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

Required files

win32/daemon  

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/pluginmanager_rb.html0000644000175000017500000000375211747546503024175 0ustar jonasjonas File: pluginmanager.rb

pluginmanager.rb

Path: lib/mcollective/pluginmanager.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/facts_rb.html0000644000175000017500000000372211747546503022441 0ustar jonasjonas File: facts.rb

facts.rb

Path: lib/mcollective/facts.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/runnerstats_rb.html0000644000175000017500000000374411747546503023735 0ustar jonasjonas File: runnerstats.rb

runnerstats.rb

Path: lib/mcollective/runnerstats.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/logger/0000755000175000017500000000000011747546503021243 5ustar jonasjonasmcollective-2.0.0/doc/files/lib/mcollective/logger/syslog_logger_rb.html0000644000175000017500000000422311747546503025474 0ustar jonasjonas File: syslog_logger.rb

syslog_logger.rb

Path: lib/mcollective/logger/syslog_logger.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

Required files

syslog  

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/logger/base_rb.html0000644000175000017500000000373111747546503023532 0ustar jonasjonas File: base.rb

base.rb

Path: lib/mcollective/logger/base.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/logger/file_logger_rb.html0000644000175000017500000000421511747546503025074 0ustar jonasjonas File: file_logger.rb

file_logger.rb

Path: lib/mcollective/logger/file_logger.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

Required files

logger  

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/logger/console_logger_rb.html0000644000175000017500000000376711747546503025632 0ustar jonasjonas File: console_logger.rb

console_logger.rb

Path: lib/mcollective/logger/console_logger.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/logger_rb.html0000644000175000017500000000372511747546503022623 0ustar jonasjonas File: logger.rb

logger.rb

Path: lib/mcollective/logger.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/optionparser_rb.html0000644000175000017500000000374711747546503024075 0ustar jonasjonas File: optionparser.rb

optionparser.rb

Path: lib/mcollective/optionparser.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective/matcher_rb.html0000644000175000017500000000373011747546503022763 0ustar jonasjonas File: matcher.rb

matcher.rb

Path: lib/mcollective/matcher.rb
Last Update: Thu Apr 05 17:20:50 +0000 2012

[Validate]

mcollective-2.0.0/doc/files/lib/mcollective_rb.html0000644000175000017500000000470411747546503021342 0ustar jonasjonas File: mcollective.rb

mcollective.rb

Path: lib/mcollective.rb
Last Update: Tue Apr 24 23:03:29 +0000 2012

Required files

rubygems   stomp   timeout   digest/md5   optparse   singleton   socket   erb   shellwords   rbconfig   tempfile   tmpdir   mcollective/monkey_patches  

[Validate]

mcollective-2.0.0/doc/fr_class_index.html0000644000175000017500000001345211747546503017464 0ustar jonasjonas Classes

Classes

Array
Dir
MCollective
MCollective::Agents
MCollective::Application
MCollective::Applications
MCollective::Client
MCollective::Config
MCollective::Connector
MCollective::Connector::Base
MCollective::DDLValidationError
MCollective::Facts
MCollective::Facts::Base
MCollective::InvalidRPCData
MCollective::Log
MCollective::Logger
MCollective::Logger::Base
MCollective::Logger::Console_logger
MCollective::Logger::File_logger
MCollective::Logger::Syslog_logger
MCollective::Matcher
MCollective::Matcher::Parser
MCollective::Matcher::Scanner
MCollective::Message
MCollective::MissingRPCData
MCollective::MsgDoesNotMatchRequestID
MCollective::MsgTTLExpired
MCollective::NotTargettedAtUs
MCollective::Optionparser
MCollective::PluginManager
MCollective::PluginPackager
MCollective::PluginPackager::AgentDefinition
MCollective::PluginPackager::StandardDefinition
MCollective::RPC
MCollective::RPC::ActionRunner
MCollective::RPC::Agent
MCollective::RPC::Audit
MCollective::RPC::Client
MCollective::RPC::DDL
MCollective::RPC::Helpers
MCollective::RPC::Progress
MCollective::RPC::Reply
MCollective::RPC::Request
MCollective::RPC::Result
MCollective::RPC::Stats
MCollective::RPCAborted
MCollective::RPCError
MCollective::Registration
MCollective::Registration::Base
MCollective::Runner
MCollective::RunnerStats
MCollective::SSL
MCollective::Security
MCollective::Security::Base
MCollective::SecurityValidationFailed
MCollective::Shell
MCollective::UnixDaemon
MCollective::UnknownRPCAction
MCollective::UnknownRPCError
MCollective::Util
MCollective::WindowsDaemon
Symbol
mcollective-2.0.0/doc/created.rid0000644000175000017500000000004011747546503015707 0ustar jonasjonasMon, 30 Apr 2012 17:34:54 +0000 mcollective-2.0.0/doc/rdoc-style.css0000644000175000017500000001033211747546503016404 0ustar jonasjonas body { font-family: Verdana,Arial,Helvetica,sans-serif; font-size: 90%; margin: 0; margin-left: 40px; padding: 0; background: white; } h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; } h1 { font-size: 150%; } h2,h3,h4 { margin-top: 1em; } a { background: #eef; color: #039; text-decoration: none; } a:hover { background: #039; color: #eef; } /* Override the base stylesheet's Anchor inside a table cell */ td > a { background: transparent; color: #039; text-decoration: none; } /* and inside a section title */ .section-title > a { background: transparent; color: #eee; text-decoration: none; } /* === Structural elements =================================== */ div#index { margin: 0; margin-left: -40px; padding: 0; font-size: 90%; } div#index a { margin-left: 0.7em; } div#index .section-bar { margin-left: 0px; padding-left: 0.7em; background: #ccc; font-size: small; } div#classHeader, div#fileHeader { width: auto; color: white; padding: 0.5em 1.5em 0.5em 1.5em; margin: 0; margin-left: -40px; border-bottom: 3px solid #006; } div#classHeader a, div#fileHeader a { background: inherit; color: white; } div#classHeader td, div#fileHeader td { background: inherit; color: white; } div#fileHeader { background: #057; } div#classHeader { background: #048; } .class-name-in-header { font-size: 180%; font-weight: bold; } div#bodyContent { padding: 0 1.5em 0 1.5em; } div#description { padding: 0.5em 1.5em; background: #efefef; border: 1px dotted #999; } div#description h1,h2,h3,h4,h5,h6 { color: #125;; background: transparent; } div#validator-badges { text-align: center; } div#validator-badges img { border: 0; } div#copyright { color: #333; background: #efefef; font: 0.75em sans-serif; margin-top: 5em; margin-bottom: 0; padding: 0.5em 2em; } /* === Classes =================================== */ table.header-table { color: white; font-size: small; } .type-note { font-size: small; color: #DEDEDE; } .xxsection-bar { background: #eee; color: #333; padding: 3px; } .section-bar { color: #333; border-bottom: 1px solid #999; margin-left: -20px; } .section-title { background: #79a; color: #eee; padding: 3px; margin-top: 2em; margin-left: -30px; border: 1px solid #999; } .top-aligned-row { vertical-align: top } .bottom-aligned-row { vertical-align: bottom } /* --- Context section classes ----------------------- */ .context-row { } .context-item-name { font-family: monospace; font-weight: bold; color: black; } .context-item-value { font-size: small; color: #448; } .context-item-desc { color: #333; padding-left: 2em; } /* --- Method classes -------------------------- */ .method-detail { background: #efefef; padding: 0; margin-top: 0.5em; margin-bottom: 1em; border: 1px dotted #ccc; } .method-heading { color: black; background: #ccc; border-bottom: 1px solid #666; padding: 0.2em 0.5em 0 0.5em; } .method-signature { color: black; background: inherit; } .method-name { font-weight: bold; } .method-args { font-style: italic; } .method-description { padding: 0 0.5em 0 0.5em; } /* --- Source code sections -------------------- */ a.source-toggle { font-size: 90%; } div.method-source-code { background: #262626; color: #ffdead; margin: 1em; padding: 0.5em; border: 1px dashed #999; overflow: hidden; } div.method-source-code pre { color: #ffdead; overflow: hidden; } /* --- Ruby keyword styles --------------------- */ .standalone-code { background: #221111; color: #ffdead; overflow: hidden; } .ruby-constant { color: #7fffd4; background: transparent; } .ruby-keyword { color: #00ffff; background: transparent; } .ruby-ivar { color: #eedd82; background: transparent; } .ruby-operator { color: #00ffee; background: transparent; } .ruby-identifier { color: #ffdead; background: transparent; } .ruby-node { color: #ffa07a; background: transparent; } .ruby-comment { color: #b22222; font-weight: bold; background: transparent; } .ruby-regexp { color: #ffa07a; background: transparent; } .ruby-value { color: #7fffd4; background: transparent; }mcollective-2.0.0/doc/classes/0000755000175000017500000000000011747546503015243 5ustar jonasjonasmcollective-2.0.0/doc/classes/Symbol.html0000644000175000017500000000755711747546503017414 0ustar jonasjonas Class: Symbol
Class Symbol
In: lib/mcollective/monkey_patches.rb
Parent: Object

Make arrays of Symbols sortable

Methods

<=>  

Included Modules

Comparable

Public Instance methods

[Source]

   # File lib/mcollective/monkey_patches.rb, line 5
5:   def <=>(other)
6:     self.to_s <=> other.to_s
7:   end

[Validate]

mcollective-2.0.0/doc/classes/Array.html0000644000175000017500000001631711747546503017217 0ustar jonasjonas Class: Array
Class Array
In: lib/mcollective/monkey_patches.rb
Parent: Object

a method # that walks an array in groups, pass a block to call the block on each sub array

Methods

Public Instance methods

[Source]

    # File lib/mcollective/monkey_patches.rb, line 21
21:   def in_groups_of(chunk_size, padded_with=nil, &block)
22:     arr = self.clone
23: 
24:     # how many to add
25:     padding = chunk_size - (arr.size % chunk_size)
26: 
27:     # pad at the end
28:     arr.concat([padded_with] * padding) unless padding == chunk_size
29: 
30:     # how many chunks we'll make
31:     count = arr.size / chunk_size
32: 
33:     # make that many arrays
34:     result = []
35:     count.times {|s| result <<  arr[s * chunk_size, chunk_size]}
36: 
37:     if block_given?
38:       result.each_with_index do |a, i|
39:         case block.arity
40:           when 1
41:             yield(a)
42:           when 2
43:             yield(a, (i == result.size - 1))
44:           else
45:             raise "Expected 1 or 2 arguments, got #{block.arity}"
46:         end
47:       end
48:     else
49:       result
50:     end
51:   end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/0000755000175000017500000000000011747546503017451 5ustar jonasjonasmcollective-2.0.0/doc/classes/MCollective/Optionparser.html0000644000175000017500000006027611747546503023037 0ustar jonasjonas Class: MCollective::Optionparser
Class MCollective::Optionparser
In: lib/mcollective/optionparser.rb
Parent: Object

A simple helper to build cli tools that supports a uniform command line layout.

Methods

Attributes

parser  [R] 

Public Class methods

Creates a new instance of the parser, you can supply defaults and include named groups of options.

Starts a parser that defaults to verbose and that includs the filter options:

 oparser = MCollective::Optionparser.new({:verbose => true}, "filter")

Stats a parser in non verbose mode that does support discovery

 oparser = MCollective::Optionparser.new()

Starts a parser in verbose mode that does not show the common options:

 oparser = MCollective::Optionparser.new({:verbose => true}, "filter", "common")

[Source]

    # File lib/mcollective/optionparser.rb, line 20
20:     def initialize(defaults = {}, include_sections = nil, exclude_sections = nil)
21:       @parser = ::OptionParser.new
22: 
23:       @include = [include_sections].flatten
24:       @exclude = [exclude_sections].flatten
25: 
26:       @options = Util.default_options
27: 
28:       @options.merge!(defaults)
29:     end

Public Instance methods

These options will be added to most cli tools

[Source]

     # File lib/mcollective/optionparser.rb, line 128
128:     def add_common_options
129:       @parser.separator ""
130:       @parser.separator "Common Options"
131: 
132:       @parser.on('-T', '--target COLLECTIVE', 'Target messages to a specific sub collective') do |f|
133:         @options[:collective] = f
134:       end
135: 
136:       @parser.on('--dt', '--discovery-timeout SECONDS', Integer, 'Timeout for doing discovery') do |t|
137:         @options[:disctimeout] = t
138:       end
139: 
140:       @parser.on('-t', '--timeout SECONDS', Integer, 'Timeout for calling remote agents') do |t|
141:         @options[:timeout] = t
142:       end
143: 
144:       @parser.on('-q', '--quiet', 'Do not be verbose') do |v|
145:         @options[:verbose] = false
146:       end
147: 
148:       @parser.on('--ttl TTL', 'Set the message validity period') do |v|
149:         @options[:ttl] = v.to_i
150:       end
151: 
152:       @parser.on('--reply-to TARGET', 'Set a custom target for replies') do |v|
153:         @options[:reply_to] = v
154:       end
155:     end

These options will be added if you pass ‘filter’ into the include list of the constructor.

[Source]

     # File lib/mcollective/optionparser.rb, line 73
 73:     def add_filter_options
 74:       @parser.separator ""
 75:       @parser.separator "Host Filters"
 76: 
 77:       @parser.on('-W', '--with FILTER', 'Combined classes and facts filter') do |f|
 78:         f.split(" ").each do |filter|
 79:           begin
 80:             fact_parsed = parse_fact(filter)
 81:             @options[:filter]["fact"] << fact_parsed
 82:           rescue
 83:             @options[:filter]["cf_class"] << filter
 84:           end
 85:         end
 86:       end
 87: 
 88:       @parser.on('-S', '--select FILTER', 'Compound filter combining facts and classes') do |f|
 89:         @options[:filter]["compound"] << MCollective::Matcher::Parser.new(f).execution_stack
 90:       end
 91: 
 92:       @parser.on('-F', '--wf', '--with-fact fact=val', 'Match hosts with a certain fact') do |f|
 93:         fact_parsed = parse_fact(f)
 94: 
 95:         @options[:filter]["fact"] << fact_parsed if fact_parsed
 96:       end
 97: 
 98:       @parser.on('-C', '--wc', '--with-class CLASS', 'Match hosts with a certain config management class') do |f|
 99:         @options[:filter]["cf_class"] << f
100:       end
101: 
102:       @parser.on('-A', '--wa', '--with-agent AGENT', 'Match hosts with a certain agent') do |a|
103:         @options[:filter]["agent"] << a
104:       end
105: 
106:       @parser.on('-I', '--wi', '--with-identity IDENT', 'Match hosts with a certain configured identity') do |a|
107:         @options[:filter]["identity"] << a
108:       end
109:     end

These options should always be present

[Source]

     # File lib/mcollective/optionparser.rb, line 112
112:     def add_required_options
113:       @parser.on('-c', '--config FILE', 'Load configuratuion from file rather than default') do |f|
114:         @options[:config] = f
115:       end
116: 
117:       @parser.on('-v', '--verbose', 'Be verbose') do |v|
118:         @options[:verbose] = v
119:       end
120: 
121:       @parser.on('-h', '--help', 'Display this screen') do
122:         puts @parser
123:         exit! 1
124:       end
125:     end

Parse the options returning the options, you can pass a block that adds additional options to the Optionparser.

The sample below starts a parser that also prompts for —arguments in addition to the defaults. It also sets the description and shows a usage message specific to this app.

 options = oparser.parse{|parser, options|
      parser.define_head "Control the mcollective controller daemon"
      parser.banner = "Usage: sh-mcollective [options] command"

      parser.on('--arg', '--argument ARGUMENT', 'Argument to pass to agent') do |v|
          options[:argument] = v
      end
 }

Users can set default options that get parsed in using the MCOLLECTIVE_EXTRA_OPTS environemnt variable

[Source]

    # File lib/mcollective/optionparser.rb, line 48
48:     def parse(&block)
49:       yield(@parser, @options) if block_given?
50: 
51:       add_required_options
52: 
53:       add_common_options unless @exclude.include?("common")
54: 
55:       @include.each do |i|
56:         next if @exclude.include?(i)
57: 
58:         options_name = "add_#{i}_options"
59:         send(options_name)  if respond_to?(options_name)
60:       end
61: 
62:       @parser.environment("MCOLLECTIVE_EXTRA_OPTS")
63: 
64:       @parser.parse!
65: 
66:       @options[:collective] = Config.instance.main_collective unless @options[:collective]
67: 
68:       @options
69:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/WindowsDaemon.html0000644000175000017500000001552311747546503023123 0ustar jonasjonas Class: MCollective::WindowsDaemon
Class MCollective::WindowsDaemon
In: lib/mcollective/windows_daemon.rb
Parent: Win32::Daemon

Methods

Public Class methods

[Source]

    # File lib/mcollective/windows_daemon.rb, line 5
 5:     def self.daemonize_runner(pid=nil)
 6:       raise "Writing pid files are not supported on the Windows Platform" if pid
 7:       raise "The Windows Daemonizer should only be used on the Windows Platform" unless Util.windows?
 8: 
 9:       WindowsDaemon.mainloop
10:     end

Public Instance methods

[Source]

    # File lib/mcollective/windows_daemon.rb, line 12
12:     def service_main
13:       Log.debug("Starting Windows Service Daemon")
14: 
15:       runner = Runner.new(nil)
16:       runner.run
17:     end

[Source]

    # File lib/mcollective/windows_daemon.rb, line 19
19:     def service_stop
20:       Log.info("Windows service stopping")
21:       PluginManager["connector_plugin"].disconnect
22:       exit! 0
23:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Util.html0000644000175000017500000014345111747546503021264 0ustar jonasjonas Module: MCollective::Util
Module MCollective::Util
In: lib/mcollective/util.rb

Some basic utility helper methods useful to clients, agents, runner etc.

Methods

Public Class methods

Picks a config file defaults to ~/.mcollective else /etc/mcollective/client.cfg

[Source]

     # File lib/mcollective/util.rb, line 140
140:     def self.config_file_for_user
141:       # expand_path is pretty lame, it relies on HOME environment
142:       # which isnt't always there so just handling all exceptions
143:       # here as cant find reverting to default
144:       begin
145:         config = File.expand_path("~/.mcollective")
146: 
147:         unless File.readable?(config) && File.file?(config)
148:           config = "/etc/mcollective/client.cfg"
149:         end
150:       rescue Exception => e
151:         config = "/etc/mcollective/client.cfg"
152:       end
153: 
154:       return config
155:     end

Creates a standard options hash

[Source]

     # File lib/mcollective/util.rb, line 158
158:     def self.default_options
159:       {:verbose     => false,
160:         :disctimeout => 2,
161:         :timeout     => 5,
162:         :config      => config_file_for_user,
163:         :collective  => nil,
164:         :filter      => empty_filter}
165:     end

Creates an empty filter

[Source]

     # File lib/mcollective/util.rb, line 130
130:     def self.empty_filter
131:       {"fact"     => [],
132:         "cf_class" => [],
133:         "agent"    => [],
134:         "identity" => [],
135:         "compound" => []}
136:     end

Checks if the passed in filter is an empty one

[Source]

     # File lib/mcollective/util.rb, line 125
125:     def self.empty_filter?(filter)
126:       filter == empty_filter || filter == {}
127:     end

[Source]

     # File lib/mcollective/util.rb, line 250
250:     def self.eval_compound_statement(expression)
251:       if expression.values.first =~ /^\//
252:         return Util.has_cf_class?(expression.values.first)
253:       elsif expression.values.first =~ />=|<=|=|<|>/
254:         optype = expression.values.first.match(/>=|<=|=|<|>/)
255:         name, value = expression.values.first.split(optype[0])
256:         unless value.split("")[0] == "/"
257:           optype[0] == "=" ? optype = "==" : optype = optype[0]
258:         else
259:           optype = "=~"
260:         end
261: 
262:         return Util.has_fact?(name,value, optype).to_s
263:       else
264:         return Util.has_cf_class?(expression.values.first)
265:       end
266:     end

Gets the value of a specific fact, mostly just a duplicate of MCollective::Facts.get_fact but it kind of goes with the other classes here

[Source]

    # File lib/mcollective/util.rb, line 61
61:     def self.get_fact(fact)
62:       Facts.get_fact(fact)
63:     end

Finds out if this MCollective has an agent by the name passed

If the passed name starts with a / it‘s assumed to be regex and will use regex to match

[Source]

    # File lib/mcollective/util.rb, line 8
 8:     def self.has_agent?(agent)
 9:       agent = Regexp.new(agent.gsub("\/", "")) if agent.match("^/")
10: 
11:       if agent.is_a?(Regexp)
12:         if Agents.agentlist.grep(agent).size > 0
13:           return true
14:         else
15:           return false
16:         end
17:       else
18:         return Agents.agentlist.include?(agent)
19:       end
20: 
21:       false
22:     end

Checks if this node has a configuration management class by parsing the a text file with just a list of classes, recipes, roles etc. This is ala the classes.txt from puppet.

If the passed name starts with a / it‘s assumed to be regex and will use regex to match

[Source]

    # File lib/mcollective/util.rb, line 38
38:     def self.has_cf_class?(klass)
39:       klass = Regexp.new(klass.gsub("\/", "")) if klass.match("^/")
40:       cfile = Config.instance.classesfile
41: 
42:       Log.debug("Looking for configuration management classes in #{cfile}")
43: 
44:       begin
45:         File.readlines(cfile).each do |k|
46:           if klass.is_a?(Regexp)
47:             return true if k.chomp.match(klass)
48:           else
49:             return true if k.chomp == klass
50:           end
51:         end
52:       rescue Exception => e
53:         Log.warn("Parsing classes file '#{cfile}' failed: #{e.class}: #{e}")
54:       end
55: 
56:       false
57:     end

Compares fact == value,

If the passed value starts with a / it‘s assumed to be regex and will use regex to match

[Source]

     # File lib/mcollective/util.rb, line 69
 69:     def self.has_fact?(fact, value, operator)
 70: 
 71:       Log.debug("Comparing #{fact} #{operator} #{value}")
 72:       Log.debug("where :fact = '#{fact}', :operator = '#{operator}', :value = '#{value}'")
 73: 
 74:       fact = Facts[fact]
 75:       return false if fact.nil?
 76: 
 77:       fact = fact.clone
 78: 
 79:       if operator == '=~'
 80:         # to maintain backward compat we send the value
 81:         # as /.../ which is what 1.0.x needed.  this strips
 82:         # off the /'s wich is what we need here
 83:         if value =~ /^\/(.+)\/$/
 84:           value = $1
 85:         end
 86: 
 87:         return true if fact.match(Regexp.new(value))
 88: 
 89:       elsif operator == "=="
 90:         return true if fact == value
 91: 
 92:       elsif ['<=', '>=', '<', '>', '!='].include?(operator)
 93:         # Yuk - need to type cast, but to_i and to_f are overzealous
 94:         if value =~ /^[0-9]+$/ && fact =~ /^[0-9]+$/
 95:           fact = Integer(fact)
 96:           value = Integer(value)
 97:         elsif value =~ /^[0-9]+.[0-9]+$/ && fact =~ /^[0-9]+.[0-9]+$/
 98:           fact = Float(fact)
 99:           value = Float(value)
100:         end
101: 
102:         return true if eval("fact #{operator} value")
103:       end
104: 
105:       false
106:     end

Checks if the configured identity matches the one supplied

If the passed name starts with a / it‘s assumed to be regex and will use regex to match

[Source]

     # File lib/mcollective/util.rb, line 112
112:     def self.has_identity?(identity)
113:       identity = Regexp.new(identity.gsub("\/", "")) if identity.match("^/")
114: 
115:       if identity.is_a?(Regexp)
116:         return Config.instance.identity.match(identity)
117:       else
118:         return true if Config.instance.identity == identity
119:       end
120: 
121:       false
122:     end

Wrapper around PluginManager.loadclass

[Source]

     # File lib/mcollective/util.rb, line 206
206:     def self.loadclass(klass)
207:       PluginManager.loadclass(klass)
208:     end

[Source]

     # File lib/mcollective/util.rb, line 167
167:     def self.make_subscriptions(agent, type, collective=nil)
168:       config = Config.instance
169: 
170:       raise("Unknown target type #{type}") unless [:broadcast, :directed, :reply].include?(type)
171: 
172:       if collective.nil?
173:         config.collectives.map do |c|
174:           {:agent => agent, :type => type, :collective => c}
175:         end
176:       else
177:         raise("Unknown collective '#{collective}' known collectives are '#{config.collectives.join ', '}'") unless config.collectives.include?(collective)
178: 
179:         [{:agent => agent, :type => type, :collective => collective}]
180:       end
181:     end

Parse a fact filter string like foo=bar into the tuple hash thats needed

[Source]

     # File lib/mcollective/util.rb, line 211
211:     def self.parse_fact_string(fact)
212:       if fact =~ /^([^ ]+?)[ ]*=>[ ]*(.+)/
213:         return {:fact => $1, :value => $2, :operator => '>=' }
214:       elsif fact =~ /^([^ ]+?)[ ]*=<[ ]*(.+)/
215:         return {:fact => $1, :value => $2, :operator => '<=' }
216:       elsif fact =~ /^([^ ]+?)[ ]*(<=|>=|<|>|!=|==|=~)[ ]*(.+)/
217:         return {:fact => $1, :value => $3, :operator => $2 }
218:       elsif fact =~ /^(.+?)[ ]*=[ ]*\/(.+)\/$/
219:         return {:fact => $1, :value => "/#{$2}/", :operator => '=~' }
220:       elsif fact =~ /^([^= ]+?)[ ]*=[ ]*(.+)/
221:         return {:fact => $1, :value => $2, :operator => '==' }
222:       else
223:         raise "Could not parse fact #{fact} it does not appear to be in a valid format"
224:       end
225:     end

Returns the current ruby version as per RUBY_VERSION, mostly doing this here to aid testing

[Source]

     # File lib/mcollective/util.rb, line 270
270:     def self.ruby_version
271:       RUBY_VERSION
272:     end

On windows ^c can‘t interrupt the VM if its blocking on IO, so this sets up a dummy thread that sleeps and this will have the end result of being interruptable at least once a second. This is a common pattern found in Rails etc

[Source]

    # File lib/mcollective/util.rb, line 28
28:     def self.setup_windows_sleeper
29:       Thread.new { loop { sleep 1 } } if Util.windows?
30:     end

Escapes a string so it‘s safe to use in system() or backticks

Taken from Shellwords#shellescape since it‘s only in a few ruby versions

[Source]

     # File lib/mcollective/util.rb, line 230
230:     def self.shellescape(str)
231:       return "''" if str.empty?
232: 
233:       str = str.dup
234: 
235:       # Process as a single byte sequence because not all shell
236:       # implementations are multibyte aware.
237:       str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1")
238: 
239:       # A LF cannot be escaped with a backslash because a backslash + LF
240:       # combo is regarded as line continuation and simply ignored.
241:       str.gsub!(/\n/, "'\n'")
242: 
243:       return str
244:     end

Helper to subscribe to a topic on multiple collectives or just one

[Source]

     # File lib/mcollective/util.rb, line 184
184:     def self.subscribe(targets)
185:       connection = PluginManager["connector_plugin"]
186: 
187:       targets = [targets].flatten
188: 
189:       targets.each do |target|
190:         connection.subscribe(target[:agent], target[:type], target[:collective])
191:       end
192:     end

Helper to unsubscribe to a topic on multiple collectives or just one

[Source]

     # File lib/mcollective/util.rb, line 195
195:     def self.unsubscribe(targets)
196:       connection = PluginManager["connector_plugin"]
197: 
198:       targets = [targets].flatten
199: 
200:       targets.each do |target|
201:         connection.unsubscribe(target[:agent], target[:type], target[:collective])
202:       end
203:     end

[Source]

     # File lib/mcollective/util.rb, line 246
246:     def self.windows?
247:       !!(RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i)
248:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Matcher/0000755000175000017500000000000011747546503021034 5ustar jonasjonasmcollective-2.0.0/doc/classes/MCollective/Matcher/Parser.html0000644000175000017500000003702111747546503023161 0ustar jonasjonas Class: MCollective::Matcher::Parser
Class MCollective::Matcher::Parser
In: lib/mcollective/matcher/parser.rb
Parent: Object

Methods

new   parse  

Attributes

execution_stack  [R] 
scanner  [R] 

Public Class methods

[Source]

    # File lib/mcollective/matcher/parser.rb, line 6
 6:       def initialize(args)
 7:         @scanner = Scanner.new(args)
 8:         @execution_stack = []
 9:         parse
10:       end

Public Instance methods

Parse the input string, one token at a time a contruct the call stack

[Source]

    # File lib/mcollective/matcher/parser.rb, line 13
13:       def parse
14:         p_token,p_token_value = nil
15:         c_token,c_token_value = @scanner.get_token
16:         parenth = 0
17: 
18:         while (c_token != nil)
19:           @scanner.token_index += 1
20:           n_token, n_token_value = @scanner.get_token
21: 
22:           unless n_token == " "
23:             case c_token
24:             when "and"
25:               unless (n_token =~ /not|statement|\(/) || (scanner.token_index == scanner.arguments.size)
26:                 raise "Error at column #{scanner.token_index}. \nExpected 'not', 'statement' or '('. Found '#{n_token_value}'"
27:               end
28: 
29:               if p_token == nil
30:                 raise "Error at column #{scanner.token_index}. \n Expression cannot start with 'and'"
31:               elsif (p_token == "and" || p_token == "or")
32:                 raise "Error at column #{scanner.token_index}. \n #{p_token} cannot be followed by 'and'"
33:               end
34: 
35:             when "or"
36:               unless (n_token =~ /not|statement|\(/) || (scanner.token_index == scanner.arguments.size)
37:                 raise "Error at column #{scanner.token_index}. \nExpected 'not', 'statement', '('. Found '#{n_token_value}'"
38:               end
39: 
40:               if p_token == nil
41:                 raise "Error at column #{scanner.token_index}. \n Expression cannot start with 'or'"
42:               elsif (p_token == "and" || p_token == "or")
43:                 raise "Error at column #{scanner.token_index}. \n #{p_token} cannot be followed by 'or'"
44:               end
45: 
46:             when "not"
47:               unless n_token =~ /statement|\(|not/
48:                 raise "Error at column #{scanner.token_index}. \nExpected 'statement' or '('. Found '#{n_token_value}'"
49:               end
50: 
51:             when "statement"
52:               unless n_token =~ /and|or|\)/
53:                 unless scanner.token_index == scanner.arguments.size
54:                   raise "Error at column #{scanner.token_index}. \nExpected 'and', 'or', ')'. Found '#{n_token_value}'"
55:                 end
56:               end
57: 
58:             when ")"
59:               unless (n_token =~ /|and|or|not|\(/)
60:                 unless(scanner.token_index == scanner.arguments.size)
61:                   raise "Error at column #{scanner.token_index}. \nExpected 'and', 'or', 'not' or '('. Found '#{n_token_value}'"
62:                 end
63:               end
64:               parenth += 1
65: 
66:             when "("
67:               unless n_token =~ /statement|not|\(/
68:                 raise "Error at column #{scanner.token_index}. \nExpected 'statement', '(',  not. Found '#{n_token_value}'"
69:               end
70:               parenth -= 1
71: 
72:             else
73:               raise "Unexpected token found at column #{scanner.token_index}. '#{c_token_value}'"
74:             end
75: 
76:             unless n_token == " "
77:               @execution_stack << {c_token => c_token_value}
78:             end
79: 
80:             p_token, p_token_value = c_token, c_token_value
81:             c_token, c_token_value = n_token, n_token_value
82:           end
83:         end
84: 
85:         if parenth < 0
86:           raise "Error. Missing parentheses ')'."
87:         elsif parenth > 0
88:           raise "Error. Missing parentheses '('."
89:         end
90:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Matcher/Scanner.html0000644000175000017500000003301211747546503023312 0ustar jonasjonas Class: MCollective::Matcher::Scanner
Class MCollective::Matcher::Scanner
In: lib/mcollective/matcher/scanner.rb
Parent: Object

Methods

get_token   new  

Attributes

arguments  [RW] 
token_index  [RW] 

Public Class methods

[Source]

   # File lib/mcollective/matcher/scanner.rb, line 6
6:       def initialize(arguments)
7:         @token_index = 0
8:         @arguments = arguments
9:       end

Public Instance methods

Scans the input string and identifies single language tokens

[Source]

    # File lib/mcollective/matcher/scanner.rb, line 12
12:       def get_token
13:         if @token_index >= @arguments.size
14:           return nil
15:         end
16: 
17:         begin
18:           case @arguments.split("")[@token_index]
19:           when "("
20:             return "(", "("
21: 
22:           when ")"
23:             return ")", ")"
24: 
25:           when "n"
26:             if (@arguments.split("")[@token_index + 1] == "o") && (@arguments.split("")[@token_index + 2] == "t") && ((@arguments.split("")[@token_index + 3] == " ") || (@arguments.split("")[@token_index + 3] == "("))
27:               @token_index += 2
28:               return "not", "not"
29:             else
30:               gen_statement
31:             end
32: 
33:           when "!"
34:             return "not", "not"
35: 
36:           when "a"
37:             if (@arguments.split("")[@token_index + 1] == "n") && (@arguments.split("")[@token_index + 2] == "d") && ((@arguments.split("")[@token_index + 3] == " ") || (@arguments.split("")[@token_index + 3] == "("))
38:               @token_index += 2
39:               return "and", "and"
40:             else
41:               gen_statement
42:             end
43: 
44:           when "o"
45:             if (@arguments.split("")[@token_index + 1] == "r") && ((@arguments.split("")[@token_index + 2] == " ") || (@arguments.split("")[@token_index + 2] == "("))
46:               @token_index += 1
47:               return "or", "or"
48:             else
49:               gen_statement
50:             end
51: 
52:           when " "
53:             return " ", " "
54: 
55:           else
56:             gen_statement
57:           end
58:         end
59:       rescue NoMethodError => e
60:         pp e
61:         raise "Cannot end statement with 'and', 'or', 'not'"
62:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/PluginManager.html0000644000175000017500000006766611747546503023115 0ustar jonasjonas Module: MCollective::PluginManager
Module MCollective::PluginManager
In: lib/mcollective/pluginmanager.rb

A simple plugin manager, it stores one plugin each of a specific type the idea is that we can only have one security provider, one connector etc.

Methods

<<   []   clear   create_instance   delete   find   find_and_load   grep   include?   loadclass   pluginlist  

Public Class methods

Adds a plugin to the list of plugins, we expect a hash like:

   {:type => "base",
    :class => foo.new}

or like:

   {:type => "base",
    :class => "Foo::Bar"}

In the event that we already have a class with the given type an exception will be raised.

If the :class passed is a String then we will delay instantiation till the first time someone asks for the plugin, this is because most likely the registration gets done by inherited() hooks, at which point the plugin class is not final.

If we were to do a .new here the Class initialize method would get called and not the plugins, we there for only initialize the classes when they get requested via []

By default all plugin instances are cached and returned later so there‘s always a single instance. You can pass :single_instance => false when calling this to instruct it to always return a new instance when a copy is requested. This only works with sending a String for :class.

[Source]

    # File lib/mcollective/pluginmanager.rb, line 30
30:     def self.<<(plugin)
31:       plugin[:single_instance] = true unless plugin.include?(:single_instance)
32: 
33:       type = plugin[:type]
34:       klass = plugin[:class]
35:       single = plugin[:single_instance]
36: 
37:       raise("Plugin #{type} already loaded") if @plugins.include?(type)
38: 
39: 
40:       # If we get a string then store 'nil' as the instance, signalling that we'll
41:       # create the class later on demand.
42:       if klass.is_a?(String)
43:         @plugins[type] = {:loadtime => Time.now, :class => klass, :instance => nil, :single => single}
44:         Log.debug("Registering plugin #{type} with class #{klass} single_instance: #{single}")
45:       else
46:         @plugins[type] = {:loadtime => Time.now, :class => klass.class, :instance => klass, :single => true}
47:         Log.debug("Registering plugin #{type} with class #{klass.class} single_instance: true")
48:       end
49:     end

Gets a plugin by type

[Source]

    # File lib/mcollective/pluginmanager.rb, line 72
72:     def self.[](plugin)
73:       raise("No plugin #{plugin} defined") unless @plugins.include?(plugin)
74: 
75:       klass = @plugins[plugin][:class]
76: 
77:       if @plugins[plugin][:single]
78:         # Create an instance of the class if one hasn't been done before
79:         if @plugins[plugin][:instance] == nil
80:           Log.debug("Returning new plugin #{plugin} with class #{klass}")
81:           @plugins[plugin][:instance] = create_instance(klass)
82:         else
83:           Log.debug("Returning cached plugin #{plugin} with class #{klass}")
84:         end
85: 
86:         @plugins[plugin][:instance]
87:       else
88:         Log.debug("Returning new plugin #{plugin} with class #{klass}")
89:         create_instance(klass)
90:       end
91:     end

deletes all registered plugins

[Source]

    # File lib/mcollective/pluginmanager.rb, line 67
67:     def self.clear
68:       @plugins.clear
69:     end

use eval to create an instance of a class

[Source]

     # File lib/mcollective/pluginmanager.rb, line 94
 94:     def self.create_instance(klass)
 95:       begin
 96:         eval("#{klass}.new")
 97:       rescue Exception => e
 98:         raise("Could not create instance of plugin #{klass}: #{e}")
 99:       end
100:     end

Removes a plugim the list

[Source]

    # File lib/mcollective/pluginmanager.rb, line 52
52:     def self.delete(plugin)
53:       @plugins.delete(plugin) if @plugins.include?(plugin)
54:     end

Finds plugins in all configured libdirs

  find("agent")

will return an array of just agent names, for example:

  ["puppetd", "package"]

Can also be used to find files of other extensions:

  find("agent", "ddl")

Will return the same list but only of files with extension .ddl in the agent subdirectory

[Source]

     # File lib/mcollective/pluginmanager.rb, line 116
116:     def self.find(type, extension="rb")
117:       extension = ".#{extension}" unless extension.match(/^\./)
118: 
119:       plugins = []
120: 
121:       Config.instance.libdir.each do |libdir|
122:         plugdir = File.join([libdir, "mcollective", type])
123:         next unless File.directory?(plugdir)
124: 
125:         Dir.new(plugdir).grep(/#{extension}$/).map do |plugin|
126:           plugins << File.basename(plugin, extension)
127:         end
128:       end
129: 
130:       plugins.sort.uniq
131:     end

Finds and loads from disk all plugins from all libdirs that match certain criteria.

   find_and_load("pluginpackager")

Will find all .rb files in the libdir/mcollective/pluginpackager/ directory in all libdirs and load them from disk.

You can influence what plugins get loaded using a block notation:

   find_and_load("pluginpackager") do |plugin|
      plugin.match(/puppet/)
   end

This will load only plugins matching /puppet/

[Source]

     # File lib/mcollective/pluginmanager.rb, line 148
148:     def self.find_and_load(type, extension="rb")
149:       extension = ".#{extension}" unless extension.match(/^\./)
150: 
151:       klasses = find(type, extension).map do |plugin|
152:         if block_given?
153:           next unless yield(plugin)
154:         end
155: 
156:         "%s::%s::%s" % [ "MCollective", type.capitalize, plugin.capitalize ]
157:       end.compact
158: 
159:       klasses.sort.uniq.each {|klass| loadclass(klass, true)}
160:     end

Grep‘s over the plugin list and returns the list found

[Source]

     # File lib/mcollective/pluginmanager.rb, line 176
176:     def self.grep(regex)
177:       @plugins.keys.grep regex
178:     end

Finds out if we have a plugin with the given name

[Source]

    # File lib/mcollective/pluginmanager.rb, line 57
57:     def self.include?(plugin)
58:       @plugins.include?(plugin)
59:     end

Loads a class from file by doing some simple search/replace on class names and then doing a require.

[Source]

     # File lib/mcollective/pluginmanager.rb, line 164
164:     def self.loadclass(klass, squash_failures=false)
165:       fname = klass.gsub("::", "/").downcase + ".rb"
166: 
167:       Log.debug("Loading #{klass} from #{fname}")
168: 
169:       load fname
170:     rescue Exception => e
171:       Log.error("Failed to load #{klass}: #{e}")
172:       raise unless squash_failures
173:     end

Provides a list of plugins we know about

[Source]

    # File lib/mcollective/pluginmanager.rb, line 62
62:     def self.pluginlist
63:       @plugins.keys
64:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Agents.html0000644000175000017500000007043511747546503021571 0ustar jonasjonas Class: MCollective::Agents
Class MCollective::Agents
In: lib/mcollective/agents.rb
Parent: Object

A collection of agents, loads them, reloads them and dispatches messages to them. It uses the PluginManager to store, load and manage instances of plugins.

Methods

Public Class methods

Get a list of agents that we have

[Source]

     # File lib/mcollective/agents.rb, line 156
156:     def self.agentlist
157:       @@agents.keys
158:     end

[Source]

    # File lib/mcollective/agents.rb, line 5
 5:     def initialize(agents = {})
 6:       @config = Config.instance
 7:       raise ("Configuration has not been loaded, can't load agents") unless @config.configured
 8: 
 9:       @@agents = agents
10: 
11:       loadagents
12:     end

Public Instance methods

Checks if a plugin should be activated by calling activate? on it if it responds to that method else always activate it

[Source]

    # File lib/mcollective/agents.rb, line 80
80:     def activate_agent?(agent)
81:       klass = Kernel.const_get("MCollective").const_get("Agent").const_get(agent.capitalize)
82: 
83:       if klass.respond_to?("activate?")
84:         return klass.activate?
85:       else
86:         Log.debug("#{klass} does not have an activate? method, activating as default")
87:         return true
88:       end
89:     rescue Exception => e
90:       Log.warn("Agent activation check for #{agent} failed: #{e.class}: #{e}")
91:       return false
92:     end

Builds a class name string given a Agent name

[Source]

    # File lib/mcollective/agents.rb, line 73
73:     def class_for_agent(agent)
74:       "MCollective::Agent::#{agent.capitalize}"
75:     end

Deletes all agents

[Source]

    # File lib/mcollective/agents.rb, line 15
15:     def clear!
16:       @@agents.each_key do |agent|
17:         PluginManager.delete "#{agent}_agent"
18:         Util.unsubscribe(Util.make_subscriptions(agent, :broadcast))
19:       end
20: 
21:       @@agents = {}
22:     end

Dispatches a message to an agent, accepts a block that will get run if there are any replies to process from the agent

[Source]

     # File lib/mcollective/agents.rb, line 129
129:     def dispatch(request, connection)
130:       Log.debug("Dispatching a message to agent #{request.agent}")
131: 
132:       Thread.new do
133:         begin
134:           agent = PluginManager["#{request.agent}_agent"]
135: 
136:           Timeout::timeout(agent.timeout) do
137:             replies = agent.handlemsg(request.payload, connection)
138: 
139:             # Agents can decide if they wish to reply or not,
140:             # returning nil will mean nothing goes back to the
141:             # requestor
142:             unless replies == nil
143:               yield(replies)
144:             end
145:           end
146:         rescue Timeout::Error => e
147:           Log.warn("Timeout while handling message for #{request.agent}")
148:         rescue Exception => e
149:           Log.error("Execution of #{request.agent} failed: #{e}")
150:           Log.error(e.backtrace.join("\n\t\t"))
151:         end
152:       end
153:     end

searches the libdirs for agents

[Source]

     # File lib/mcollective/agents.rb, line 95
 95:     def findagentfile(agentname)
 96:       @config.libdir.each do |libdir|
 97:         agentfile = File.join([libdir, "mcollective", "agent", "#{agentname}.rb"])
 98:         if File.exist?(agentfile)
 99:           Log.debug("Found #{agentname} at #{agentfile}")
100:           return agentfile
101:         end
102:       end
103:       return false
104:     end

Returns the help for an agent after first trying to get rid of some indentation infront

[Source]

     # File lib/mcollective/agents.rb, line 113
113:     def help(agentname)
114:       raise("No such agent") unless include?(agentname)
115: 
116:       body = PluginManager["#{agentname}_agent"].help.split("\n")
117: 
118:       if body.first =~ /^(\s+)\S/
119:         indent = $1
120: 
121:         body = body.map {|b| b.gsub(/^#{indent}/, "")}
122:       end
123: 
124:       body.join("\n")
125:     end

Determines if we have an agent with a certain name

[Source]

     # File lib/mcollective/agents.rb, line 107
107:     def include?(agentname)
108:       PluginManager.include?("#{agentname}_agent")
109:     end

Loads a specified agent from disk if available

[Source]

    # File lib/mcollective/agents.rb, line 42
42:     def loadagent(agentname)
43:       agentfile = findagentfile(agentname)
44:       return false unless agentfile
45:       classname = class_for_agent(agentname)
46: 
47:       PluginManager.delete("#{agentname}_agent")
48: 
49:       begin
50:         single_instance = ["registration", "discovery"].include?(agentname)
51: 
52:         PluginManager.loadclass(classname)
53: 
54:         if activate_agent?(agentname)
55:           PluginManager << {:type => "#{agentname}_agent", :class => classname, :single_instance => single_instance}
56: 
57:           Util.subscribe(Util.make_subscriptions(agentname, :broadcast)) unless @@agents.include?(agentname)
58: 
59:           @@agents[agentname] = {:file => agentfile}
60:           return true
61:         else
62:           Log.debug("Not activating agent #{agentname} due to agent policy in activate? method")
63:           return false
64:         end
65:       rescue Exception => e
66:         Log.error("Loading agent #{agentname} failed: #{e}")
67:         PluginManager.delete("#{agentname}_agent")
68:         return false
69:       end
70:     end

Loads all agents from disk

[Source]

    # File lib/mcollective/agents.rb, line 25
25:     def loadagents
26:       Log.debug("Reloading all agents from disk")
27: 
28:       clear!
29: 
30:       @config.libdir.each do |libdir|
31:         agentdir = "#{libdir}/mcollective/agent"
32:         next unless File.directory?(agentdir)
33: 
34:         Dir.new(agentdir).grep(/\.rb$/).each do |agent|
35:           agentname = File.basename(agent, ".rb")
36:           loadagent(agentname) unless PluginManager.include?("#{agentname}_agent")
37:         end
38:       end
39:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/SecurityValidationFailed.html0000644000175000017500000000447111747546503025274 0ustar jonasjonas Class: MCollective::SecurityValidationFailed
Class MCollective::SecurityValidationFailed
In: lib/mcollective.rb
Parent: RuntimeError

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Connector.html0000644000175000017500000000616111747546503022275 0ustar jonasjonas Module: MCollective::Connector
Module MCollective::Connector
In: lib/mcollective/connector/base.rb
lib/mcollective/connector.rb

Connectors take care of transporting messages between clients and agents, the design doesn‘t your middleware to be very rich in features. All it really needs is the ability to send and receive messages to named queues/topics.

At present there are assumptions about the naming of topics and queues that is compatible with Stomp, ie.

/topic/foo.bar/baz /queue/foo.bar/baz

This is the only naming format that is supported, but you could replace Stomp with something else that supports the above, see MCollective::Connector::Stomp for the default connector.

Classes and Modules

Class MCollective::Connector::Base

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Config.html0000644000175000017500000011636411747546503021557 0ustar jonasjonas Class: MCollective::Config
Class MCollective::Config
In: lib/mcollective/config.rb
Parent: Object

A pretty sucky config class, ripe for refactoring/improving

Methods

Included Modules

Singleton

Attributes

classesfile  [R] 
collectives  [R] 
color  [R] 
configdir  [R] 
configfile  [R] 
configured  [R] 
connector  [R] 
daemonize  [R] 
daemonize  [R] 
direct_addressing  [R] 
direct_addressing_threshold  [R] 
fact_cache_time  [R] 
factsource  [R] 
identity  [R] 
keeplogs  [R] 
libdir  [R] 
logfacility  [R] 
logfile  [R] 
logger_type  [R] 
loglevel  [R] 
main_collective  [R] 
max_log_size  [R] 
pluginconf  [R] 
queueprefix  [R] 
registerinterval  [R] 
registration  [R] 
registration_collective  [R] 
rpcaudit  [R] 
rpcauditprovider  [R] 
rpcauthorization  [R] 
rpcauthprovider  [R] 
rpchelptemplate  [R] 
rpclimitmethod  [R] 
securityprovider  [R] 
ssl_cipher  [R] 
topicprefix  [R] 
topicsep  [R] 
ttl  [R] 

Public Class methods

[Source]

    # File lib/mcollective/config.rb, line 15
15:     def initialize
16:       @configured = false
17:     end

Public Instance methods

[Source]

     # File lib/mcollective/config.rb, line 19
 19:     def loadconfig(configfile)
 20:       set_config_defaults(configfile)
 21: 
 22:       if File.exists?(configfile)
 23:         File.open(configfile, "r").each do |line|
 24: 
 25:           # strip blank spaces, tabs etc off the end of all lines
 26:           line.gsub!(/\s*$/, "")
 27: 
 28:           unless line =~ /^#|^$/
 29:             if (line =~ /(.+?)\s*=\s*(.+)/)
 30:               key = $1
 31:               val = $2
 32: 
 33:               case key
 34:                 when "topicsep"
 35:                   @topicsep = val
 36:                 when "registration"
 37:                   @registration = val.capitalize
 38:                 when "registration_collective"
 39:                   @registration_collective = val
 40:                 when "registerinterval"
 41:                   @registerinterval = val.to_i
 42:                 when "collectives"
 43:                   @collectives = val.split(",").map {|c| c.strip}
 44:                 when "main_collective"
 45:                   @main_collective = val
 46:                 when "topicprefix"
 47:                   @topicprefix = val
 48:                 when "queueprefix"
 49:                   @queueprefix = val
 50:                 when "logfile"
 51:                   @logfile = val
 52:                 when "keeplogs"
 53:                   @keeplogs = val.to_i
 54:                 when "max_log_size"
 55:                   @max_log_size = val.to_i
 56:                 when "loglevel"
 57:                   @loglevel = val
 58:                 when "logfacility"
 59:                   @logfacility = val
 60:                 when "libdir"
 61:                   paths = val.split(File::PATH_SEPARATOR)
 62:                   paths.each do |path|
 63:                     @libdir << path
 64:                     unless $LOAD_PATH.include?(path)
 65:                       $LOAD_PATH << path
 66:                     end
 67:                   end
 68:                 when "identity"
 69:                   @identity = val
 70:                 when "direct_addressing"
 71:                   val =~ /^1|y/i ? @direct_addressing = true : @direct_addressing = false
 72:                 when "direct_addressing_threshold"
 73:                   @direct_addressing_threshold = val.to_i
 74:                 when "color"
 75:                   val =~ /^1|y/i ? @color = true : @color = false
 76:                 when "daemonize"
 77:                   val =~ /^1|y/i ? @daemonize = true : @daemonize = false
 78:                 when "securityprovider"
 79:                   @securityprovider = val.capitalize
 80:                 when "factsource"
 81:                   @factsource = val.capitalize
 82:                 when "connector"
 83:                   @connector = val.capitalize
 84:                 when "classesfile"
 85:                   @classesfile = val
 86:                 when /^plugin.(.+)$/
 87:                   @pluginconf[$1] = val
 88:                 when "rpcaudit"
 89:                   val =~ /^1|y/i ? @rpcaudit = true : @rpcaudit = false
 90:                 when "rpcauditprovider"
 91:                   @rpcauditprovider = val.capitalize
 92:                 when "rpcauthorization"
 93:                   val =~ /^1|y/i ? @rpcauthorization = true : @rpcauthorization = false
 94:                 when "rpcauthprovider"
 95:                   @rpcauthprovider = val.capitalize
 96:                 when "rpchelptemplate"
 97:                   @rpchelptemplate = val
 98:                 when "rpclimitmethod"
 99:                   @rpclimitmethod = val.to_sym
100:                 when "logger_type"
101:                   @logger_type = val
102:                 when "fact_cache_time"
103:                   @fact_cache_time = val.to_i
104:                 when "ssl_cipher"
105:                   @ssl_cipher = val
106:                 when "ttl"
107:                   @ttl = val.to_i
108:                 else
109:                   raise("Unknown config parameter #{key}")
110:               end
111:             end
112:           end
113:         end
114: 
115:         read_plugin_config_dir("#{@configdir}/plugin.d")
116: 
117:         raise 'Identities can only match /\w\.\-/' unless @identity.match(/^[\w\.\-]+$/)
118: 
119:         @configured = true
120: 
121:         @libdir.each {|dir| Log.warn("Cannot find libdir: #{dir}") unless File.directory?(dir)}
122: 
123:         if @logger_type == "syslog"
124:           raise "The sylog logger is not usable on the Windows platform" if Util.windows?
125:         end
126: 
127:         PluginManager.loadclass("Mcollective::Facts::#{@factsource}_facts")
128:         PluginManager.loadclass("Mcollective::Connector::#{@connector}")
129:         PluginManager.loadclass("Mcollective::Security::#{@securityprovider}")
130:         PluginManager.loadclass("Mcollective::Registration::#{@registration}")
131:         PluginManager.loadclass("Mcollective::Audit::#{@rpcauditprovider}") if @rpcaudit
132:         PluginManager << {:type => "global_stats", :class => RunnerStats.new}
133:       else
134:         raise("Cannot find config file '#{configfile}'")
135:       end
136:     end

[Source]

     # File lib/mcollective/config.rb, line 182
182:     def read_plugin_config_dir(dir)
183:       return unless File.directory?(dir)
184: 
185:       Dir.new(dir).each do |pluginconfigfile|
186:         next unless pluginconfigfile =~ /^([\w]+).cfg$/
187: 
188:         plugin = $1
189:         File.open("#{dir}/#{pluginconfigfile}", "r").each do |line|
190:           # strip blank lines
191:           line.gsub!(/\s*$/, "")
192:           next if line =~ /^#|^$/
193:           if (line =~ /(.+?)\s*=\s*(.+)/)
194:             key = $1
195:             val = $2
196:             @pluginconf["#{plugin}.#{key}"] = val
197:           end
198:         end
199:       end
200:     end

[Source]

     # File lib/mcollective/config.rb, line 138
138:     def set_config_defaults(configfile)
139:       @stomp = Hash.new
140:       @subscribe = Array.new
141:       @pluginconf = Hash.new
142:       @connector = "Stomp"
143:       @securityprovider = "Psk"
144:       @factsource = "Yaml"
145:       @identity = Socket.gethostname
146:       @registration = "Agentlist"
147:       @registerinterval = 0
148:       @registration_collective = nil
149:       @topicsep = "."
150:       @topicprefix = "/topic/"
151:       @queueprefix = "/queue/"
152:       @classesfile = "/var/lib/puppet/state/classes.txt"
153:       @rpcaudit = false
154:       @rpcauditprovider = ""
155:       @rpcauthorization = false
156:       @rpcauthprovider = ""
157:       @configdir = File.dirname(configfile)
158:       @color = !Util.windows?
159:       @configfile = configfile
160:       @logger_type = "file"
161:       @keeplogs = 5
162:       @max_log_size = 2097152
163:       @rpclimitmethod = :first
164:       @libdir = Array.new
165:       @fact_cache_time = 300
166:       @loglevel = "info"
167:       @logfacility = "user"
168:       @collectives = ["mcollective"]
169:       @main_collective = @collectives.first
170:       @ssl_cipher = "aes-256-cbc"
171:       @direct_addressing = false
172:       @direct_addressing_threshold = 10
173:       @ttl = 60
174: 
175:       # look in the config dir for the template so users can provide their own and windows
176:       # with odd paths will just work more often, but fall back to old behavior if it does
177:       # not exist
178:       @rpchelptemplate = File.join(File.dirname(configfile), "rpc-help.erb")
179:       @rpchelptemplate = "/etc/mcollective/rpc-help.erb" unless File.exists?(@rpchelptemplate)
180:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/UnixDaemon.html0000644000175000017500000001735111747546503022415 0ustar jonasjonas Class: MCollective::UnixDaemon
Class MCollective::UnixDaemon
In: lib/mcollective/unix_daemon.rb
Parent: Object

Methods

Public Class methods

Daemonize the current process

[Source]

    # File lib/mcollective/unix_daemon.rb, line 4
 4:     def self.daemonize
 5:       fork do
 6:         Process.setsid
 7:         exit if fork
 8:         Dir.chdir('/tmp')
 9:         STDIN.reopen('/dev/null')
10:         STDOUT.reopen('/dev/null', 'a')
11:         STDERR.reopen('/dev/null', 'a')
12: 
13:         yield
14:       end
15:     end

[Source]

    # File lib/mcollective/unix_daemon.rb, line 17
17:     def self.daemonize_runner(pid=nil)
18:       raise "The Unix Daemonizer can not be used on the Windows Platform" if Util.windows?
19: 
20:       UnixDaemon.daemonize do
21:         if pid
22:           begin
23:             File.open(pid, 'w') {|f| f.write(Process.pid) }
24:           rescue Exception => e
25:           end
26:         end
27: 
28:         begin
29:           runner = Runner.new(nil)
30:           runner.run
31:         ensure
32:           File.unlink(pid) if pid && File.exist?(pid)
33:         end
34:       end
35:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/NotTargettedAtUs.html0000644000175000017500000000445111747546503023544 0ustar jonasjonas Class: MCollective::NotTargettedAtUs
Class MCollective::NotTargettedAtUs
In: lib/mcollective.rb
Parent: RuntimeError

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RunnerStats.html0000644000175000017500000004225111747546503022633 0ustar jonasjonas Class: MCollective::RunnerStats
Class MCollective::RunnerStats
In: lib/mcollective/runnerstats.rb
Parent: Object

Class to store stats about the mcollectived, it should live in the PluginManager so that agents etc can get hold of it and return the stats to callers

Methods

filtered   new   passed   received   sent   to_hash   ttlexpired   unvalidated   validated  

Public Class methods

[Source]

    # File lib/mcollective/runnerstats.rb, line 5
 5:     def initialize
 6:       @starttime = Time.now.to_i
 7:       @validated = 0
 8:       @unvalidated = 0
 9:       @filtered = 0
10:       @passed = 0
11:       @total = 0
12:       @replies = 0
13:       @ttlexpired = 0
14: 
15:       @mutex = Mutex.new
16:     end

Public Instance methods

Records a message that didnt pass the filters

[Source]

    # File lib/mcollective/runnerstats.rb, line 31
31:     def filtered
32:       Log.debug("Incrementing filtered stat")
33:       @filtered += 1
34:     end

Records a message that passed the filters

[Source]

    # File lib/mcollective/runnerstats.rb, line 25
25:     def passed
26:       Log.debug("Incrementing passed stat")
27:       @passed += 1
28:     end

Records receipt of a message

[Source]

    # File lib/mcollective/runnerstats.rb, line 48
48:     def received
49:       Log.debug("Incrementing total stat")
50:       @total += 1
51:     end

Records sending a message

[Source]

    # File lib/mcollective/runnerstats.rb, line 54
54:     def sent
55:       @mutex.synchronize do
56:         Log.debug("Incrementing replies stat")
57:         @replies += 1
58:       end
59:     end

Returns a hash with all stats

[Source]

    # File lib/mcollective/runnerstats.rb, line 62
62:     def to_hash
63:       stats = {:validated => @validated,
64:         :unvalidated => @unvalidated,
65:         :passed => @passed,
66:         :filtered => @filtered,
67:         :starttime => @starttime,
68:         :total => @total,
69:         :ttlexpired => @ttlexpired,
70:         :replies => @replies}
71: 
72:       reply = {:stats => stats,
73:         :threads => [],
74:         :pid => Process.pid,
75:         :times => {} }
76: 
77:       ::Process.times.each_pair{|k,v|
78:         k = k.to_sym
79:         reply[:times][k] = v
80:       }
81: 
82:       Thread.list.each do |t|
83:         reply[:threads] << "#{t.inspect}"
84:       end
85: 
86:       reply[:agents] = Agents.agentlist
87:       reply
88:     end

Records a message that failed TTL checks

[Source]

    # File lib/mcollective/runnerstats.rb, line 19
19:     def ttlexpired
20:       Log.debug("Incrementing ttl expired stat")
21:       @ttlexpired += 1
22:     end

[Source]

    # File lib/mcollective/runnerstats.rb, line 42
42:     def unvalidated
43:       Log.debug("Incrementing unvalidated stat")
44:       @unvalidated += 1
45:     end

Records a message that validated ok

[Source]

    # File lib/mcollective/runnerstats.rb, line 37
37:     def validated
38:       Log.debug("Incrementing validated stat")
39:       @validated += 1
40:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/MsgTTLExpired.html0000644000175000017500000000444311747546503022777 0ustar jonasjonas Class: MCollective::MsgTTLExpired
Class MCollective::MsgTTLExpired
In: lib/mcollective.rb
Parent: RuntimeError

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RPC.html0000644000175000017500000006603711747546503020777 0ustar jonasjonas Module: MCollective::RPC
Module MCollective::RPC
In: lib/mcollective/rpc/reply.rb
lib/mcollective/rpc/request.rb
lib/mcollective/rpc/audit.rb
lib/mcollective/rpc/result.rb
lib/mcollective/rpc/progress.rb
lib/mcollective/rpc/actionrunner.rb
lib/mcollective/rpc/agent.rb
lib/mcollective/rpc/ddl.rb
lib/mcollective/rpc/client.rb
lib/mcollective/rpc/helpers.rb
lib/mcollective/rpc/stats.rb
lib/mcollective/rpc.rb

Toolset to create a standard interface of client and agent using an RPC metaphor, standard compliant agents will make it easier to create generic clients like web interfaces etc

Methods

Classes and Modules

Class MCollective::RPC::ActionRunner
Class MCollective::RPC::Agent
Class MCollective::RPC::Audit
Class MCollective::RPC::Client
Class MCollective::RPC::DDL
Class MCollective::RPC::Helpers
Class MCollective::RPC::Progress
Class MCollective::RPC::Reply
Class MCollective::RPC::Request
Class MCollective::RPC::Result
Class MCollective::RPC::Stats

Public Class methods

means for other classes to drop discovered hosts into this module its a bit hacky but needed so that the mixin methods like printrpcstats can easily get access to it without users having to pass it around in params.

[Source]

     # File lib/mcollective/rpc.rb, line 108
108:     def self.discovered(discovered)
109:       @@discovered = discovered
110:     end

Factory for RPC::Reply messages, only really here to make agents a bit easier to understand

[Source]

     # File lib/mcollective/rpc.rb, line 184
184:     def self.reply
185:       RPC::Reply.new
186:     end

Factory for RPC::Request messages, only really here to make agents a bit easier to understand

[Source]

     # File lib/mcollective/rpc.rb, line 178
178:     def self.request(msg)
179:       RPC::Request.new(msg)
180:     end

means for other classes to drop stats into this module its a bit hacky but needed so that the mixin methods like printrpcstats can easily get access to it without users having to pass it around in params.

[Source]

     # File lib/mcollective/rpc.rb, line 100
100:     def self.stats(stats)
101:       @@stats = stats
102:     end

Public Instance methods

Wrapper for MCollective::Util.empty_filter? to make clients less fugly to write - ticket 18

[Source]

     # File lib/mcollective/rpc.rb, line 168
168:     def empty_filter?(options)
169:       if options.include?(:filter)
170:         Util.empty_filter?(options[:filter])
171:       else
172:         Util.empty_filter?(options)
173:       end
174:     end

Prints the result of an RPC call.

In the default quiet mode - no flattening or verbose - only results that produce an error will be printed

To get details of each result run with the -v command line option.

[Source]

     # File lib/mcollective/rpc.rb, line 148
148:     def printrpc(result, flags = {})
149:       verbose = @options[:verbose] rescue verbose = false
150:       verbose = flags[:verbose] || verbose
151:       flatten = flags[:flatten] || false
152:       format = @options[:output_format]
153: 
154:       result_text =  Helpers.rpcresults(result, {:verbose => verbose, :flatten => flatten, :format => format})
155: 
156:       if result.is_a?(Array) && format == :console
157:         puts "\n%s\n" % [ result_text ]
158:       else
159:         # when we get just one result to print dont pad them all with
160:         # blank spaces etc, just print the individual result with no
161:         # padding
162:         puts result_text unless result_text == ""
163:       end
164:     end

Prints stats, requires stats to be saved from elsewhere using the MCollective::RPC.stats method.

If you‘ve passed -v on the command line a detailed stat block will be printed, else just a one liner.

You can pass flags into it, at the moment only one flag is supported:

printrpcstats :caption => "Foo"

This will use "Foo" as the caption to the stats in verbose mode

[Source]

     # File lib/mcollective/rpc.rb, line 125
125:     def printrpcstats(flags={})
126:       return unless @options[:output_format] == :console
127: 
128:       verbose = @options[:verbose] rescue verbose = false
129:       caption = flags[:caption] || "rpc stats"
130: 
131:       begin
132:         stats = @@stats
133:       rescue
134:         puts("no stats to display")
135:         return
136:       end
137: 
138:       puts
139:       puts stats.report(caption, verbose)
140:     end

Wrapper to create clients, supposed to be used as a mixin:

include MCollective::RPC

exim = rpcclient("exim") printrpc exim.mailq

or

rpcclient("exim") do |exim|

   printrpc exim.mailq

end

It will take a few flags:

   :configfile => "etc/client.cfg"
   :options => options
   :exit_on_failure => true

Options would be a build up options hash from the Optionparser you can use the rpcoptions helper to create this

:exit_on_failure is true by default, and causes the application to exit if there is a failure constructing the RPC client. Set this flag to false to cause an Exception to be raised instead.

[Source]

    # File lib/mcollective/rpc.rb, line 61
61:     def rpcclient(agent, flags = {})
62:       configfile = flags[:configfile] || "/etc/mcollective/client.cfg"
63:       options = flags[:options] || nil
64: 
65:       if flags.key?(:exit_on_failure)
66:         exit_on_failure = flags[:exit_on_failure]
67:       else
68:         # We exit on failure by default for CLI-friendliness
69:         exit_on_failure = true
70:       end
71: 
72:       begin
73:         if options
74:           rpc = Client.new(agent, :configfile => options[:config], :options => options)
75:           @options = rpc.options
76:         else
77:           rpc = Client.new(agent, :configfile => configfile)
78:           @options = rpc.options
79:         end
80:       rescue Exception => e
81:         if exit_on_failure
82:           puts("Could not create RPC client: #{e}")
83:           exit!
84:         else
85:           raise e
86:         end
87:       end
88: 
89:       if block_given?
90:         yield(rpc)
91:       else
92:         return rpc
93:       end
94:     end

Creates a standard options hash, pass in a block to add extra headings etc see Optionparser

[Source]

    # File lib/mcollective/rpc.rb, line 22
22:     def rpcoptions
23:       oparser = MCollective::Optionparser.new({:verbose => false, :progress_bar => true}, "filter")
24: 
25:       options = oparser.parse do |parser, options|
26:         if block_given?
27:           yield(parser, options)
28:         end
29: 
30:         Helpers.add_simplerpc_options(parser, options)
31:       end
32: 
33:       return options
34:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Facts.html0000644000175000017500000001630711747546503021406 0ustar jonasjonas Module: MCollective::Facts
Module MCollective::Facts
In: lib/mcollective/facts.rb
lib/mcollective/facts/base.rb

This is a class that gives access to the configured fact provider such as MCollectives::Facts::Facter that uses Reductive Labs facter

The actual provider is pluggable and configurable using the ‘factsource’ configuration option.

To develop a new factsource simply create a class under MCollective::Facts:: and provide the following classes:

  self.get_fact(fact)
  self.has_fact?(fact)

You can also just inherit from MCollective::Facts::Base and provide just the

  self.get_facts

method that should return a hash of facts.

Methods

[]   get_fact   has_fact?  

Classes and Modules

Class MCollective::Facts::Base

Public Class methods

Get the value of a fact

[Source]

    # File lib/mcollective/facts.rb, line 35
35:     def self.[](fact)
36:       PluginManager["facts_plugin"].get_fact(fact)
37:     end

Get the value of a fact

[Source]

    # File lib/mcollective/facts.rb, line 30
30:     def self.get_fact(fact)
31:       PluginManager["facts_plugin"].get_fact(fact)
32:     end

True if we know of a specific fact else false

[Source]

    # File lib/mcollective/facts.rb, line 25
25:     def self.has_fact?(fact, value)
26:       PluginManager["facts_plugin"].get_fact(fact) == value ? true : false
27:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RPCError.html0000644000175000017500000000460511747546503022002 0ustar jonasjonas Class: MCollective::RPCError
Class MCollective::RPCError
In: lib/mcollective.rb
Parent: StandardError

Exceptions for the RPC system

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Matcher.html0000644000175000017500000000647011747546503021731 0ustar jonasjonas Module: MCollective::Matcher
Module MCollective::Matcher
In: lib/mcollective/matcher/scanner.rb
lib/mcollective/matcher/parser.rb
lib/mcollective/matcher.rb

A parser and scanner that creates a stack machine for a simple fact and class matching language used on the CLI to facilitate a rich discovery language

Language EBNF

compound = ["("] expression [")"] {["("] expression [")"]} expression = [!|not]statement ["and"|"or"] [!|not] statement char = A-Z | a-z | < | > | => | =< | _ | - |* | / { A-Z | a-z | < | > | => | =< | _ | - | * | / | } int = 0|1|2|3|4|5|6|7|8|9{|0|1|2|3|4|5|6|7|8|9|0}

Classes and Modules

Class MCollective::Matcher::Parser
Class MCollective::Matcher::Scanner

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Message.html0000644000175000017500000011254511747546503021733 0ustar jonasjonas Class: MCollective::Message
Class MCollective::Message
In: lib/mcollective/message.rb
Parent: Object

container for a message, its headers, agent, collective and other meta data

Methods

Constants

VALIDTYPES = [:message, :request, :direct_request, :reply]

Attributes

agent  [RW] 
collective  [RW] 
discovered_hosts  [RW] 
expected_msgid  [R] 
filter  [RW] 
headers  [RW] 
message  [R] 
msgtime  [R] 
options  [RW] 
payload  [R] 
reply_to  [R] 
request  [R] 
requestid  [RW] 
ttl  [RW] 
type  [R] 
validated  [R] 

Public Class methods

payload - the message body without headers etc, just the text message - the original message received from the middleware options[:base64] - if the body base64 encoded? options[:agent] - the agent the message is for/from options[:collective] - the collective its for/from options[:headers] - the message headers options[:type] - an indicator about the type of message, :message, :request, :direct_request or :reply options[:request] - if this is a reply this should old the message we are replying to options[:filter] - for requests, the filter to encode into the message options[:options] - the normal client options hash options[:ttl] - the maximum amount of seconds this message can be valid for options[:expected_msgid] - in the case of replies this is the msgid it is expecting in the replies

[Source]

    # File lib/mcollective/message.rb, line 22
22:     def initialize(payload, message, options = {})
23:       options = {:base64 => false,
24:                  :agent => nil,
25:                  :headers => {},
26:                  :type => :message,
27:                  :request => nil,
28:                  :filter => Util.empty_filter,
29:                  :options => {},
30:                  :ttl => 60,
31:                  :expected_msgid => nil,
32:                  :collective => nil}.merge(options)
33: 
34:       @payload = payload
35:       @message = message
36:       @requestid = nil
37:       @discovered_hosts = nil
38:       @reply_to = nil
39: 
40:       @type = options[:type]
41:       @headers = options[:headers]
42:       @base64 = options[:base64]
43:       @filter = options[:filter]
44:       @expected_msgid = options[:expected_msgid]
45:       @options = options[:options]
46: 
47:       @ttl = @options[:ttl] || Config.instance.ttl
48:       @msgtime = 0
49: 
50:       @validated = false
51: 
52:       if options[:request]
53:         @request = options[:request]
54:         @agent = request.agent
55:         @collective = request.collective
56:         @type = :reply
57:       else
58:         @agent = options[:agent]
59:         @collective = options[:collective]
60:       end
61: 
62:       base64_decode!
63:     end

Public Instance methods

[Source]

     # File lib/mcollective/message.rb, line 121
121:     def base64?
122:       @base64
123:     end

[Source]

     # File lib/mcollective/message.rb, line 107
107:     def base64_decode!
108:       return unless @base64
109: 
110:       @payload = SSL.base64_decode(@payload)
111:       @base64 = false
112:     end

[Source]

     # File lib/mcollective/message.rb, line 114
114:     def base64_encode!
115:       return if @base64
116: 
117:       @payload = SSL.base64_encode(@payload)
118:       @base64 = true
119:     end

[Source]

     # File lib/mcollective/message.rb, line 197
197:     def create_reqid
198:       Digest::MD5.hexdigest("#{Config.instance.identity}-#{Time.now.to_f}-#{agent}-#{collective}")
199:     end

[Source]

     # File lib/mcollective/message.rb, line 141
141:     def decode!
142:       raise "Cannot decode message type #{type}" unless [:request, :reply].include?(type)
143: 
144:       @payload = PluginManager["security_plugin"].decodemsg(self)
145: 
146:       if type == :request
147:         raise 'callerid in request is not valid, surpressing reply to potentially forged request' unless PluginManager["security_plugin"].valid_callerid?(payload[:callerid])
148:       end
149: 
150:       [:collective, :agent, :filter, :requestid, :ttl, :msgtime].each do |prop|
151:         instance_variable_set("@#{prop}", payload[prop]) if payload.include?(prop)
152:       end
153:     end

[Source]

     # File lib/mcollective/message.rb, line 125
125:     def encode!
126:       case type
127:         when :reply
128:           raise "Cannot encode a reply message if no request has been associated with it" unless request
129:           raise 'callerid in original request is not valid, surpressing reply to potentially forged request' unless PluginManager["security_plugin"].valid_callerid?(request.payload[:callerid])
130: 
131:           @requestid = request.payload[:requestid]
132:           @payload = PluginManager["security_plugin"].encodereply(agent, payload, requestid, request.payload[:callerid])
133:         when :request, :direct_request
134:           @requestid = create_reqid
135:           @payload = PluginManager["security_plugin"].encoderequest(Config.instance.identity, payload, requestid, filter, agent, collective, ttl)
136:         else
137:           raise "Cannot encode #{type} messages"
138:       end
139:     end

in the case of reply messages we are expecting replies to a previously created message. This stores a hint to that previously sent message id and can be used by other classes like the security plugins as a means of optimizing their behavior like by ignoring messages not directed at us.

[Source]

     # File lib/mcollective/message.rb, line 102
102:     def expected_msgid=(msgid)
103:       raise "Can only store the expected msgid for reply messages" unless @type == :reply
104:       @expected_msgid = msgid
105:     end

publish a reply message by creating a target name and sending it

[Source]

     # File lib/mcollective/message.rb, line 179
179:     def publish
180:       Timeout.timeout(2) do
181:         # If we've been specificaly told about hosts that were discovered
182:         # use that information to do P2P calls if appropriate else just
183:         # send it as is.
184:         if @discovered_hosts && Config.instance.direct_addressing
185:           if @discovered_hosts.size <= Config.instance.direct_addressing_threshold
186:             @type = :direct_request
187:             Log.debug("Handling #{requestid} as a direct request")
188:           end
189: 
190:           PluginManager["connector_plugin"].publish(self)
191:         else
192:           PluginManager["connector_plugin"].publish(self)
193:         end
194:       end
195:     end

Sets a custom reply-to target for requests. The connector plugin should inspect this when constructing requests and set this header ensuring replies will go to the custom target otherwise the connector should just do what it usually does

[Source]

    # File lib/mcollective/message.rb, line 91
91:     def reply_to=(target)
92:       raise "Custom reply targets can only be set on requests" unless [:request, :direct_request].include?(@type)
93: 
94:       @reply_to = target
95:     end

Sets the message type to one of the known types. In the case of :direct_request the list of hosts to communicate with should have been set with discovered_hosts else an exception will be raised. This is for extra security, we never accidentally want to send a direct request without a list of hosts or something weird like that as it might result in a filterless broadcast being sent.

Additionally you simply cannot set :direct_request if direct_addressing was not enabled this is to force a workflow that doesnt not yield in a mistake when someone might assume direct_addressing is enabled when its not.

[Source]

    # File lib/mcollective/message.rb, line 74
74:     def type=(type)
75:       if type == :direct_request
76:         raise "Direct requests is not enabled using the direct_addressing config option" unless Config.instance.direct_addressing
77: 
78:         unless @discovered_hosts && !@discovered_hosts.empty?
79:           raise "Can only set type to :direct_request if discovered_hosts have been set"
80:         end
81:       end
82: 
83:       raise "Unknown message type #{type}" unless VALIDTYPES.include?(type)
84: 
85:       @type = type
86:     end

Perform validation against the message by checking filters and ttl

[Source]

     # File lib/mcollective/message.rb, line 156
156:     def validate
157:       raise "Can only validate request messages" unless type == :request
158: 
159:       msg_age = Time.now.utc.to_i - msgtime
160: 
161:       if msg_age > ttl
162:         cid = ""
163:         cid += payload[:callerid] + "@" if payload.include?(:callerid)
164:         cid += payload[:senderid]
165: 
166:         if msg_age > ttl
167:           PluginManager["global_stats"].ttlexpired
168: 
169:           raise(MsgTTLExpired, "Message #{requestid} from #{cid} created at #{msgtime} is #{msg_age} seconds old, TTL is #{ttl}")
170:         end
171:       end
172: 
173:       raise(NotTargettedAtUs, "Received message is not targetted to us") unless PluginManager["security_plugin"].validate_filter?(payload[:filter])
174: 
175:       @validated = true
176:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Security/0000755000175000017500000000000011747546503021260 5ustar jonasjonasmcollective-2.0.0/doc/classes/MCollective/Security/Base.html0000644000175000017500000011264611747546503023032 0ustar jonasjonas Class: MCollective::Security::Base
Class MCollective::Security::Base
In: lib/mcollective/security/base.rb
Parent: Object

This is a base class the other security modules should inherit from it handles statistics and validation of messages that should in most cases apply to all security models.

To create your own security plugin you should provide a plugin that inherits from this and provides the following methods:

decodemsg - Decodes a message that was received from the middleware encodereply - Encodes a reply message to a previous request message encoderequest - Encodes a new request message validrequest? - Validates a request received from the middleware

Optionally if you are identifying users by some other means like certificate name you can provide your own callerid method that can provide the rest of the system with an id, and you would see this id being usable in SimpleRPC authorization methods

The @initiated_by variable will be set to either :client or :node depending on who is using this plugin. This is to help security providers that operate in an asymetric mode like public/private key based systems.

Specifics of each of these are a bit fluid and the interfaces for this is not set in stone yet, specifically the encode methods will be provided with a helper that takes care of encoding the core requirements. The best place to see how security works is by looking at the provided MCollective::Security::PSK plugin.

Methods

Attributes

initiated_by  [RW] 
stats  [R] 

Public Class methods

Register plugins that inherits base

[Source]

    # File lib/mcollective/security/base.rb, line 32
32:       def self.inherited(klass)
33:         PluginManager << {:type => "security_plugin", :class => klass.to_s}
34:       end

Initializes configuration and logging as well as prepare a zero‘d hash of stats various security methods and filter validators should increment stats, see MCollective::Security::Psk for a sample

[Source]

    # File lib/mcollective/security/base.rb, line 38
38:       def initialize
39:         @config = Config.instance
40:         @log = Log
41:         @stats = PluginManager["global_stats"]
42:       end

Public Instance methods

Returns a unique id for the caller, by default we just use the unix user id, security plugins can provide their own means of doing ids.

[Source]

     # File lib/mcollective/security/base.rb, line 212
212:       def callerid
213:         "uid=#{Process.uid}"
214:       end

[Source]

     # File lib/mcollective/security/base.rb, line 160
160:       def create_reply(reqid, agent, body)
161:         Log.debug("Encoded a message for request #{reqid}")
162: 
163:         {:senderid => @config.identity,
164:           :requestid => reqid,
165:           :senderagent => agent,
166:           :msgtime => Time.now.utc.to_i,
167:           :body => body}
168:       end

[Source]

     # File lib/mcollective/security/base.rb, line 170
170:       def create_request(reqid, filter, msg, initiated_by, target_agent, target_collective, ttl=60)
171:         Log.debug("Encoding a request for agent '#{target_agent}' in collective #{target_collective} with request id #{reqid}")
172: 
173:         {:body => msg,
174:           :senderid => @config.identity,
175:           :requestid => reqid,
176:           :filter => filter,
177:           :collective => target_collective,
178:           :agent => target_agent,
179:           :callerid => callerid,
180:           :ttl => ttl,
181:           :msgtime => Time.now.utc.to_i}
182:       end

Security providers should provide this, see MCollective::Security::Psk

[Source]

     # File lib/mcollective/security/base.rb, line 232
232:       def decodemsg(msg)
233:         Log.error("decodemsg is not implimented in #{self.class}")
234:       end

Security providers should provide this, see MCollective::Security::Psk

[Source]

     # File lib/mcollective/security/base.rb, line 227
227:       def encodereply(sender, msg, requestcallerid=nil)
228:         Log.error("encodereply is not implimented in #{self.class}")
229:       end

Security providers should provide this, see MCollective::Security::Psk

[Source]

     # File lib/mcollective/security/base.rb, line 222
222:       def encoderequest(sender, msg, filter={})
223:         Log.error("encoderequest is not implimented in #{self.class}")
224:       end

Give a MC::Message instance and a message id this will figure out if you the incoming message id matches the one the Message object is expecting and raise if its not

Mostly used by security plugins to figure out if they should do the hard work of decrypting etc messages that would only later on be ignored

[Source]

     # File lib/mcollective/security/base.rb, line 189
189:       def should_process_msg?(msg, msgid)
190:         if msg.expected_msgid
191:           unless msg.expected_msgid == msgid
192:             msgtext = "Got a message with id %s but was expecting %s, ignoring message" % [msgid, msg.expected_msgid]
193:             Log.debug msgtext
194:             raise MsgDoesNotMatchRequestID, msgtext
195:           end
196:         end
197: 
198:         true
199:       end

Validates a callerid. We do not want to allow things like \ and / in callerids since other plugins make assumptions that these are safe strings.

callerids are generally in the form uid=123 or cert=foo etc so we do that here but security plugins could override this for some complex uses

[Source]

     # File lib/mcollective/security/base.rb, line 206
206:       def valid_callerid?(id)
207:         !!id.match(/^[\w]+=[\w\.\-]+$/)
208:       end

Takes a Hash with a filter in it and validates it against host information.

At present this supports filter matches against the following criteria:

  • puppet_class|cf_class - Presence of a configuration management class in
                            the file configured with classesfile
    
  • agent - Presence of a MCollective agent with a supplied name
  • fact - The value of a fact avout this system
  • identity - the configured identity of the system

TODO: Support REGEX and/or multiple filter keys to be AND‘d

[Source]

     # File lib/mcollective/security/base.rb, line 55
 55:       def validate_filter?(filter)
 56:         failed = 0
 57:         passed = 0
 58: 
 59:         passed = 1 if Util.empty_filter?(filter)
 60: 
 61:         filter.keys.each do |key|
 62:           case key
 63:           when /puppet_class|cf_class/
 64:             filter[key].each do |f|
 65:               Log.debug("Checking for class #{f}")
 66:               if Util.has_cf_class?(f) then
 67:                 Log.debug("Passing based on configuration management class #{f}")
 68:                 passed += 1
 69:               else
 70:                 Log.debug("Failing based on configuration management class #{f}")
 71:                 failed += 1
 72:               end
 73:             end
 74: 
 75:           when "compound"
 76:             filter[key].each do |compound|
 77:               result = []
 78: 
 79:               compound.each do |expression|
 80:                 case expression.keys.first
 81:                 when "statement"
 82:                   result << Util.eval_compound_statement(expression).to_s
 83:                 when "and"
 84:                   result << "&&"
 85:                 when "or"
 86:                   result << "||"
 87:                 when "("
 88:                   result << "("
 89:                 when ")"
 90:                   result << ")"
 91:                 when "not"
 92:                   result << "!"
 93:                 end
 94:               end
 95: 
 96:               result = eval(result.join(" "))
 97: 
 98:               if result
 99:                 Log.debug("Passing based on class and fact composition")
100:                 passed +=1
101:               else
102:                 Log.debug("Failing based on class and fact composition")
103:                 failed +=1
104:               end
105:             end
106: 
107:           when "agent"
108:             filter[key].each do |f|
109:               if Util.has_agent?(f) || f == "mcollective"
110:                 Log.debug("Passing based on agent #{f}")
111:                 passed += 1
112:               else
113:                 Log.debug("Failing based on agent #{f}")
114:                 failed += 1
115:               end
116:             end
117: 
118:           when "fact"
119:             filter[key].each do |f|
120:               if Util.has_fact?(f[:fact], f[:value], f[:operator])
121:                 Log.debug("Passing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}")
122:                 passed += 1
123:               else
124:                 Log.debug("Failing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}")
125:                 failed += 1
126:               end
127:             end
128: 
129:           when "identity"
130:             unless filter[key].empty?
131:               # Identity filters should not be 'and' but 'or' as each node can only have one identity
132:               matched = filter[key].select{|f| Util.has_identity?(f)}.size
133: 
134:               if matched == 1
135:                 Log.debug("Passing based on identity")
136:                 passed += 1
137:               else
138:                 Log.debug("Failed based on identity")
139:                 failed += 1
140:               end
141:             end
142:           end
143:         end
144: 
145:         if failed == 0 && passed > 0
146:           Log.debug("Message passed the filter checks")
147: 
148:           @stats.passed
149: 
150:           return true
151:         else
152:           Log.debug("Message failed the filter checks")
153: 
154:           @stats.filtered
155: 
156:           return false
157:         end
158:       end

Security providers should provide this, see MCollective::Security::Psk

[Source]

     # File lib/mcollective/security/base.rb, line 217
217:       def validrequest?(req)
218:         Log.error("validrequest? is not implimented in #{self.class}")
219:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/InvalidRPCData.html0000644000175000017500000000453611747546503023074 0ustar jonasjonas Class: MCollective::InvalidRPCData
Class MCollective::InvalidRPCData
In: lib/mcollective.rb
Parent: RPCError

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RPC/0000755000175000017500000000000011747546503020075 5ustar jonasjonasmcollective-2.0.0/doc/classes/MCollective/RPC/Reply.html0000644000175000017500000002770011747546503022064 0ustar jonasjonas Class: MCollective::RPC::Reply
Class MCollective::RPC::Reply
In: lib/mcollective/rpc/reply.rb
Parent: Object

Simple class to manage compliant replies to MCollective::RPC

Methods

[]   []=   fail   fail!   new   to_hash  

Attributes

data  [RW] 
statuscode  [RW] 
statusmsg  [RW] 

Public Class methods

[Source]

    # File lib/mcollective/rpc/reply.rb, line 7
 7:       def initialize
 8:         @data = {}
 9:         @statuscode = 0
10:         @statusmsg = "OK"
11:       end

Public Instance methods

Read from the data hash

[Source]

    # File lib/mcollective/rpc/reply.rb, line 48
48:       def [](key)
49:         @data[key]
50:       end

Write to the data hash

[Source]

    # File lib/mcollective/rpc/reply.rb, line 43
43:       def []=(key, val)
44:         @data[key] = val
45:       end

Helper to fill in statusmsg and code on failure

[Source]

    # File lib/mcollective/rpc/reply.rb, line 14
14:       def fail(msg, code=1)
15:         @statusmsg = msg
16:         @statuscode = code
17:       end

Helper that fills in statusmsg and code but also raises an appropriate error

[Source]

    # File lib/mcollective/rpc/reply.rb, line 20
20:       def fail!(msg, code=1)
21:         @statusmsg = msg
22:         @statuscode = code
23: 
24:         case code
25:         when 1
26:           raise RPCAborted, msg
27: 
28:         when 2
29:           raise UnknownRPCAction, msg
30: 
31:         when 3
32:           raise MissingRPCData, msg
33: 
34:         when 4
35:           raise InvalidRPCData, msg
36: 
37:         else
38:           raise UnknownRPCError, msg
39:         end
40:       end

Returns a compliant Hash of the reply that should be sent over the middleware

[Source]

    # File lib/mcollective/rpc/reply.rb, line 54
54:       def to_hash
55:         return {:statuscode => @statuscode,
56:           :statusmsg => @statusmsg,
57:           :data => @data}
58:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RPC/Result.html0000644000175000017500000002504611747546503022250 0ustar jonasjonas Class: MCollective::RPC::Result
Class MCollective::RPC::Result
In: lib/mcollective/rpc/result.rb
Parent: Object

Simple class to manage compliant results from MCollective::RPC agents

Currently it just fakes Hash behaviour to the result to remain backward compatible but it also knows which agent and action produced it so you can associate results to a DDL

Methods

[]   []=   each   new   to_json  

Included Modules

Enumerable

Attributes

action  [R] 
agent  [R] 
results  [R] 

Public Class methods

[Source]

    # File lib/mcollective/rpc/result.rb, line 13
13:       def initialize(agent, action, result={})
14:         @agent = agent
15:         @action = action
16:         @results = result
17:       end

Public Instance methods

[Source]

    # File lib/mcollective/rpc/result.rb, line 19
19:       def [](idx)
20:         @results[idx]
21:       end

[Source]

    # File lib/mcollective/rpc/result.rb, line 23
23:       def []=(idx, item)
24:         @results[idx] = item
25:       end

[Source]

    # File lib/mcollective/rpc/result.rb, line 27
27:       def each
28:         @results.each_pair {|k,v| yield(k,v) }
29:       end

[Source]

    # File lib/mcollective/rpc/result.rb, line 31
31:       def to_json(*a)
32:         {:agent => @agent,
33:           :action => @action,
34:           :sender => @results[:sender],
35:           :statuscode => @results[:statuscode],
36:           :statusmsg => @results[:statusmsg],
37:           :data => @results[:data]}.to_json(*a)
38:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RPC/Agent.html0000644000175000017500000006002611747546503022025 0ustar jonasjonas Class: MCollective::RPC::Agent
Class MCollective::RPC::Agent
In: lib/mcollective/rpc/agent.rb
Parent: Object

A wrapper around the traditional agent, it takes care of a lot of the tedious setup you would do for each agent allowing you to just create methods following a naming standard leaving the heavy lifting up to this clas.

See marionette-collective.org/simplerpc/agents.html

It only really makes sense to use this with a Simple RPC client on the other end, basic usage would be:

   module MCollective
      module Agent
         class Helloworld<RPC::Agent
            matadata :name        => "Test SimpleRPC Agent",
                     :description => "A simple test",
                     :author      => "You",
                     :license     => "1.1",
                     :url         => "http://your.com/,
                     :timeout     => 60

            action "hello" do
                reply[:msg] = "Hello #{request[:name]}"
            end

            action "foo" do
                implemented_by "/some/script.sh"
            end
         end
      end
   end

If you wish to implement the logic for an action using an external script use the implemented_by method that will cause your script to be run with 2 arguments.

The first argument is a file containing JSON with the request and the 2nd argument is where the script should save its output as a JSON hash.

We also currently have the validation code in here, this will be moved to plugins soon.

Methods

actions   activate?   handlemsg   help   help   new  

Attributes

config  [R] 
ddl  [R] 
logger  [R] 
meta  [RW] 
reply  [RW] 
request  [RW] 
timeout  [R] 

Public Class methods

Returns an array of actions this agent support

[Source]

     # File lib/mcollective/rpc/agent.rb, line 176
176:       def self.actions
177:         public_instance_methods.sort.grep(/_action$/).map do |method|
178:           $1 if method =~ /(.+)_action$/
179:         end
180:       end

By default RPC Agents support a toggle in the configuration that can enable and disable them based on the agent name

Example an agent called Foo can have:

plugin.foo.activate_agent = false

and this will prevent the agent from loading on this particular machine.

Agents can use the activate_when helper to override this for example:

activate_when do

   File.exist?("/usr/bin/puppet")

end

[Source]

     # File lib/mcollective/rpc/agent.rb, line 143
143:       def self.activate?
144:         agent_name = self.to_s.split("::").last.downcase
145: 
146:         Log.debug("Starting default activation checks for #{agent_name}")
147: 
148:         should_activate = Config.instance.pluginconf["#{agent_name}.activate_agent"]
149: 
150:         if should_activate
151:           Log.debug("Found plugin config #{agent_name}.activate_agent with value #{should_activate}")
152:           unless should_activate =~ /^1|y|true$/
153:             return false
154:           end
155:         end
156: 
157:         return true
158:       end

Generates help using the template based on the data created with metadata and input

[Source]

     # File lib/mcollective/rpc/agent.rb, line 162
162:       def self.help(template)
163:         if @ddl
164:           @ddl.help(template)
165:         else
166:           "No DDL defined"
167:         end
168:       end

[Source]

    # File lib/mcollective/rpc/agent.rb, line 44
44:       def initialize
45:         # Default meta data unset
46:         @meta = {:timeout     => 10,
47:                  :name        => "Unknown",
48:                  :description => "Unknown",
49:                  :author      => "Unknown",
50:                  :license     => "Unknown",
51:                  :version     => "Unknown",
52:                  :url         => "Unknown"}
53: 
54:         @timeout = meta[:timeout] || 10
55:         @logger = Log.instance
56:         @config = Config.instance
57:         @agent_name = self.class.to_s.split("::").last.downcase
58: 
59:         # Loads the DDL so we can later use it for validation
60:         # and help generation
61:         begin
62:           @ddl = DDL.new(@agent_name)
63:         rescue Exception => e
64:           Log.debug("Failed to load DDL for agent: #{e.class}: #{e}")
65:           @ddl = nil
66:         end
67: 
68:         # if we have a global authorization provider enable it
69:         # plugins can still override it per plugin
70:         self.class.authorized_by(@config.rpcauthprovider) if @config.rpcauthorization
71: 
72:         startup_hook
73:       end

Public Instance methods

[Source]

     # File lib/mcollective/rpc/agent.rb, line 75
 75:       def handlemsg(msg, connection)
 76:         @request = RPC.request(msg)
 77:         @reply = RPC.reply
 78: 
 79:         begin
 80:           # Calls the authorization plugin if any is defined
 81:           # if this raises an exception we wil just skip processing this
 82:           # message
 83:           authorization_hook(@request) if respond_to?("authorization_hook")
 84: 
 85: 
 86:           # Audits the request, currently continues processing the message
 87:           # we should make this a configurable so that an audit failure means
 88:           # a message wont be processed by this node depending on config
 89:           audit_request(@request, connection)
 90: 
 91:           before_processing_hook(msg, connection)
 92: 
 93:           if respond_to?("#{@request.action}_action")
 94:             send("#{@request.action}_action")
 95:           else
 96:             raise UnknownRPCAction, "Unknown action: #{@request.action}"
 97:           end
 98:         rescue RPCAborted => e
 99:           @reply.fail e.to_s, 1
100: 
101:         rescue UnknownRPCAction => e
102:           @reply.fail e.to_s, 2
103: 
104:         rescue MissingRPCData => e
105:           @reply.fail e.to_s, 3
106: 
107:         rescue InvalidRPCData => e
108:           @reply.fail e.to_s, 4
109: 
110:         rescue UnknownRPCError => e
111:           @reply.fail e.to_s, 5
112: 
113:         rescue Exception => e
114:           @reply.fail e.to_s, 5
115: 
116:         end
117: 
118:         after_processing_hook
119: 
120:         if @request.should_respond?
121:           return @reply.to_hash
122:         else
123:           Log.debug("Client did not request a response, surpressing reply")
124:           return nil
125:         end
126:       end

to auto generate help

[Source]

     # File lib/mcollective/rpc/agent.rb, line 171
171:       def help
172:         self.help("#{@config[:configdir]}/rpc-help.erb")
173:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RPC/Audit.html0000644000175000017500000001425711747546503022042 0ustar jonasjonas Class: MCollective::RPC::Audit
Class MCollective::RPC::Audit
In: lib/mcollective/rpc/audit.rb
Parent: Object

Auditing of requests is done only for SimpleRPC requests, you provide a plugin in the MCollective::Audit::* namespace which the SimpleRPC framework calls for each message

We provide a simple one that logs to a logfile in the class MCollective::Audit::Logfile you can create your own:

Create a class in plugins/mcollective/audit/<yourplugin>.rb

You must inherit from MCollective::RPC::Audit which will take care of registering you with the plugin system.

Your plugin must provide audit_request(request, connection) the request parameter will be an instance of MCollective::RPC::Request

To enable auditing you should set:

rpcaudit = 1 rpcauditprovider = Logfile

in the config file this will enable logging using the MCollective::Audit::Logile class

The Audit class acts as a base for audit plugins and takes care of registering them with the plugin manager

Methods

Public Class methods

[Source]

    # File lib/mcollective/rpc/audit.rb, line 29
29:       def self.inherited(klass)
30:         PluginManager << {:type => "rpcaudit_plugin", :class => klass.to_s}
31:       end

Public Instance methods

[Source]

    # File lib/mcollective/rpc/audit.rb, line 33
33:       def audit_request(request, connection)
34:         @log.error("audit_request is not implimented in #{this.class}")
35:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RPC/ActionRunner.html0000644000175000017500000006611711747546503023405 0ustar jonasjonas Class: MCollective::RPC::ActionRunner
Class MCollective::RPC::ActionRunner
In: lib/mcollective/rpc/actionrunner.rb
Parent: Object

A helper used by RPC::Agent#implemented_by to delegate an action to an external script. At present only JSON based serialization is supported in future ones based on key=val pairs etc will be added

It serializes the request object into an input file and creates an empty output file. It then calls the external command reading the output file at the end.

any STDERR gets logged at error level and any STDOUT gets logged at info level.

It will interpret the exit code from the application the same way RPC::Reply#fail! and fail handles their codes creating a consistent interface, the message part of the fail message will come from STDERR

Generally externals should just exit with code 1 on failure and print to STDERR, this is exactly what Perl die() does and translates perfectly to our model

It uses the MCollective::Shell wrapper to call the external application

Methods

Attributes

action  [R] 
agent  [R] 
command  [R] 
format  [R] 
request  [R] 
stderr  [R] 
stdout  [R] 

Public Class methods

[Source]

    # File lib/mcollective/rpc/actionrunner.rb, line 26
26:       def initialize(command, request, format=:json)
27:         @agent = request.agent
28:         @action = request.action
29:         @format = format
30:         @request = request
31:         @command = path_to_command(command)
32:         @stdout = ""
33:         @stderr = ""
34:       end

Public Instance methods

[Source]

     # File lib/mcollective/rpc/actionrunner.rb, line 117
117:       def canrun?(command)
118:         File.executable?(command)
119:       end

[Source]

    # File lib/mcollective/rpc/actionrunner.rb, line 91
91:       def load_json_results(file)
92:         return {} unless File.readable?(file)
93: 
94:         JSON.load(File.read(file))
95:       rescue JSON::ParserError
96:         {}
97:       end

[Source]

    # File lib/mcollective/rpc/actionrunner.rb, line 73
73:       def load_results(file)
74:         Log.debug("Attempting to load results in #{format} format from #{file}")
75: 
76:         data = {}
77: 
78:         if respond_to?("load_#{format}_results")
79:           tempdata = send("load_#{format}_results", file)
80: 
81:           tempdata.each_pair do |k,v|
82:             data[k.to_sym] = v
83:           end
84:         end
85: 
86:         data
87:       rescue Exception => e
88:         {}
89:       end

[Source]

     # File lib/mcollective/rpc/actionrunner.rb, line 129
129:       def path_to_command(command)
130:         unless command[0,1] == File::SEPARATOR
131:           Config.instance.libdir.each do |libdir|
132:             command_file = File.join(libdir, "agent", agent, command)
133: 
134:             return command_file if File.exist?(command_file)
135:           end
136:         end
137: 
138:         return command
139:       end

[Source]

    # File lib/mcollective/rpc/actionrunner.rb, line 36
36:       def run
37:         unless canrun?(command)
38:           Log.warn("Cannot run #{to_s}")
39:           raise RPCAborted, "Cannot execute #{to_s}"
40:         end
41: 
42:         Log.debug("Running #{to_s}")
43: 
44:         request_file = saverequest(request)
45:         reply_file = tempfile("reply")
46:         reply_file.close
47: 
48:         runner = shell(command, request_file.path, reply_file.path)
49: 
50:         runner.runcommand
51: 
52:         Log.debug("#{command} exited with #{runner.status.exitstatus}")
53: 
54:         stderr.each_line {|l| Log.error("#{to_s}: #{l.chomp}")} unless stderr.empty?
55:         stdout.each_line {|l| Log.info("#{to_s}: #{l.chomp}")} unless stdout.empty?
56: 
57:         {:exitstatus => runner.status.exitstatus,
58:          :stdout     => runner.stdout,
59:          :stderr     => runner.stderr,
60:          :data       => load_results(reply_file.path)}
61:       ensure
62:         request_file.close! if request_file.respond_to?("close!")
63:         reply_file.close! if reply_file.respond_to?("close")
64:       end

[Source]

     # File lib/mcollective/rpc/actionrunner.rb, line 113
113:       def save_json_request(req)
114:         req.to_json
115:       end

[Source]

     # File lib/mcollective/rpc/actionrunner.rb, line 99
 99:       def saverequest(req)
100:         Log.debug("Attempting to save request in #{format} format")
101: 
102:         if respond_to?("save_#{format}_request")
103:           data = send("save_#{format}_request", req)
104: 
105:           request_file = tempfile("request")
106:           request_file.puts data
107:           request_file.close
108:         end
109: 
110:         request_file
111:       end

[Source]

    # File lib/mcollective/rpc/actionrunner.rb, line 66
66:       def shell(command, infile, outfile)
67:         env = {"MCOLLECTIVE_REQUEST_FILE" => infile,
68:                "MCOLLECTIVE_REPLY_FILE"   => outfile}
69: 
70:         Shell.new("#{command} #{infile} #{outfile}", :cwd => Dir.tmpdir, :stdout => stdout, :stderr => stderr, :environment => env)
71:       end

[Source]

     # File lib/mcollective/rpc/actionrunner.rb, line 125
125:       def tempfile(prefix)
126:         Tempfile.new("mcollective_#{prefix}", Dir.tmpdir)
127:       end

[Source]

     # File lib/mcollective/rpc/actionrunner.rb, line 121
121:       def to_s
122:         "%s#%s command: %s" % [ agent, action, command ]
123:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RPC/Client.html0000644000175000017500000022753311747546503022215 0ustar jonasjonas Class: MCollective::RPC::Client
Class MCollective::RPC::Client
In: lib/mcollective/rpc/client.rb
Parent: Object

The main component of the Simple RPC client system, this wraps around MCollective::Client and just brings in a lot of convention and standard approached.

Methods

Attributes

agent  [R] 
batch_mode  [R] 
batch_size  [R] 
batch_sleep_time  [R] 
client  [R] 
config  [RW] 
ddl  [R] 
discovery_timeout  [RW] 
filter  [RW] 
limit_method  [R] 
limit_targets  [R] 
output_format  [R] 
progress  [RW] 
reply_to  [RW] 
stats  [R] 
timeout  [RW] 
ttl  [RW] 
verbose  [RW] 

Public Class methods

Creates a stub for a remote agent, you can pass in an options array in the flags which will then be used else it will just create a default options array with filtering enabled based on the standard command line use.

  rpc = RPC::Client.new("rpctest", :configfile => "client.cfg", :options => options)

You typically would not call this directly you‘d use MCollective::RPC#rpcclient instead which is a wrapper around this that can be used as a Mixin

[Source]

     # File lib/mcollective/rpc/client.rb, line 19
 19:       def initialize(agent, flags = {})
 20:         if flags.include?(:options)
 21:           initial_options = flags[:options]
 22: 
 23:         elsif @@initial_options
 24:           initial_options = Marshal.load(@@initial_options)
 25: 
 26:         else
 27:           oparser = MCollective::Optionparser.new({:verbose => false, :progress_bar => true, :mcollective_limit_targets => false, :batch_size => nil, :batch_sleep_time => 1}, "filter")
 28: 
 29:           initial_options = oparser.parse do |parser, opts|
 30:             if block_given?
 31:               yield(parser, opts)
 32:             end
 33: 
 34:             Helpers.add_simplerpc_options(parser, opts)
 35:           end
 36: 
 37:           @@initial_options = Marshal.dump(initial_options)
 38:         end
 39: 
 40:         @stats = Stats.new
 41:         @agent = agent
 42:         @discovery_timeout = initial_options[:disctimeout]
 43:         @timeout = initial_options[:timeout]
 44:         @verbose = initial_options[:verbose]
 45:         @filter = initial_options[:filter]
 46:         @config = initial_options[:config]
 47:         @discovered_agents = nil
 48:         @progress = initial_options[:progress_bar]
 49:         @limit_targets = initial_options[:mcollective_limit_targets]
 50:         @limit_method = Config.instance.rpclimitmethod
 51:         @output_format = initial_options[:output_format] || :console
 52:         @force_direct_request = false
 53:         @reply_to = initial_options[:reply_to]
 54: 
 55:         @batch_size = Integer(initial_options[:batch_size] || 0)
 56:         @batch_sleep_time = Float(initial_options[:batch_sleep_time] || 1)
 57:         @batch_mode = @batch_size > 0
 58: 
 59:         agent_filter agent
 60: 
 61:         @client = MCollective::Client.new(@config)
 62:         @client.options = initial_options
 63: 
 64:         @collective = @client.collective
 65:         @ttl = initial_options[:ttl] || Config.instance.ttl
 66: 
 67:         # if we can find a DDL for the service override
 68:         # the timeout of the client so we always magically
 69:         # wait appropriate amounts of time.
 70:         #
 71:         # We add the discovery timeout to the ddl supplied
 72:         # timeout as the discovery timeout tends to be tuned
 73:         # for local network conditions and fact source speed
 74:         # which would other wise not be accounted for and
 75:         # some results might get missed.
 76:         #
 77:         # We do this only if the timeout is the default 5
 78:         # seconds, so that users cli overrides will still
 79:         # get applied
 80:         begin
 81:           @ddl = DDL.new(agent)
 82:           @timeout = @ddl.meta[:timeout] + @discovery_timeout if @timeout == 5
 83:         rescue Exception => e
 84:           Log.debug("Could not find DDL: #{e}")
 85:           @ddl = nil
 86:         end
 87: 
 88:         # allows stderr and stdout to be overridden for testing
 89:         # but also for web apps that might not want a bunch of stuff
 90:         # generated to actual file handles
 91:         if initial_options[:stderr]
 92:           @stderr = initial_options[:stderr]
 93:         else
 94:           @stderr = STDERR
 95:           @stderr.sync = true
 96:         end
 97: 
 98:         if initial_options[:stdout]
 99:           @stdout = initial_options[:stdout]
100:         else
101:           @stdout = STDOUT
102:           @stdout.sync = true
103:         end
104:       end

Public Instance methods

Sets the agent filter

[Source]

     # File lib/mcollective/rpc/client.rb, line 347
347:       def agent_filter(agent)
348:         @filter["agent"] << agent
349:         @filter["agent"].compact!
350:         reset
351:       end

Sets the batch size, if the size is set to 0 that will disable batch mode

[Source]

     # File lib/mcollective/rpc/client.rb, line 519
519:       def batch_size=(limit)
520:         raise "Can only set batch size if direct addressing is supported" unless Config.instance.direct_addressing
521: 
522:         @batch_size = Integer(limit)
523:         @batch_mode = @batch_size > 0
524:       end

[Source]

     # File lib/mcollective/rpc/client.rb, line 526
526:       def batch_sleep_time=(time)
527:         raise "Can only set batch sleep time if direct addressing is supported" unless Config.instance.direct_addressing
528: 
529:         @batch_sleep_time = Float(time)
530:       end

Sets the class filter

[Source]

     # File lib/mcollective/rpc/client.rb, line 323
323:       def class_filter(klass)
324:         @filter["cf_class"] << klass
325:         @filter["cf_class"].compact!
326:         reset
327:       end

Sets the collective we are communicating with

[Source]

     # File lib/mcollective/rpc/client.rb, line 487
487:       def collective=(c)
488:         @collective = c
489:         @client.options[:collective] = c
490:       end

Set a compound filter

[Source]

     # File lib/mcollective/rpc/client.rb, line 361
361:       def compound_filter(filter)
362:         @filter["compound"] << Matcher::Parser.new(filter).execution_stack
363:         reset
364:       end

Constructs custom requests with custom filters and discovery data the idea is that this would be used in web applications where you might be using a cached copy of data provided by a registration agent to figure out on your own what nodes will be responding and what your filter would be.

This will help you essentially short circuit the traditional cycle of:

mc discover / call / wait for discovered nodes

by doing discovery however you like, contructing a filter and a list of nodes you expect responses from.

Other than that it will work exactly like a normal call, blocks will behave the same way, stats will be handled the same way etcetc

If you just wanted to contact one machine for example with a client that already has other filter options setup you can do:

puppet.custom_request("runonce", {}, ["your.box.com"], {:identity => "your.box.com"})

This will do runonce action on just ‘your.box.com’, no discovery will be done and after receiving just one response it will stop waiting for responses

If direct_addressing is enabled in the config file you can provide an empty hash as a filter, this will force that request to be a directly addressed request which technically does not need filters. If you try to use this mode with direct addressing disabled an exception will be raise

[Source]

     # File lib/mcollective/rpc/client.rb, line 274
274:       def custom_request(action, args, expected_agents, filter = {}, &block)
275:         @ddl.validate_request(action, args) if @ddl
276: 
277:         if filter == {} && !Config.instance.direct_addressing
278:           raise "Attempted to do a filterless custom_request without direct_addressing enabled, preventing unexpected call to all nodes"
279:         end
280: 
281:         @stats.reset
282: 
283:         custom_filter = Util.empty_filter
284:         custom_options = options.clone
285: 
286:         # merge the supplied filter with the standard empty one
287:         # we could just use the merge method but I want to be sure
288:         # we dont merge in stuff that isnt actually valid
289:         ["identity", "fact", "agent", "cf_class", "compound"].each do |ftype|
290:           if filter.include?(ftype)
291:             custom_filter[ftype] = [filter[ftype], custom_filter[ftype]].flatten
292:           end
293:         end
294: 
295:         # ensure that all filters at least restrict the call to the agent we're a proxy for
296:         custom_filter["agent"] << @agent unless custom_filter["agent"].include?(@agent)
297:         custom_options[:filter] = custom_filter
298: 
299:         # Fake out the stats discovery would have put there
300:         @stats.discovered_agents([expected_agents].flatten)
301: 
302:         # Handle fire and forget requests
303:         #
304:         # If a specific reply-to was set then from the client perspective this should
305:         # be a fire and forget request too since no response will ever reach us - it
306:         # will go to the reply-to destination
307:         if args[:process_results] == false || @reply_to
308:           return fire_and_forget_request(action, args, custom_filter)
309:         end
310: 
311:         # Now do a call pretty much exactly like in method_missing except with our own
312:         # options and discovery magic
313:         if block_given?
314:           call_agent(action, args, custom_options, [expected_agents].flatten) do |r|
315:             block.call(r)
316:           end
317:         else
318:           call_agent(action, args, custom_options, [expected_agents].flatten)
319:         end
320:       end

Disconnects cleanly from the middleware

[Source]

     # File lib/mcollective/rpc/client.rb, line 107
107:       def disconnect
108:         @client.disconnect
109:       end

Does discovery based on the filters set, if a discovery was previously done return that else do a new discovery.

Alternatively if identity filters are given and none of them are regular expressions then just use the provided data as discovered data, avoiding discovery

Discovery can be forced if direct_addressing is enabled by passing in an array of nodes with :nodes or JSON data like those produced by mcollective RPC JSON output using :json

Will show a message indicating its doing discovery if running verbose or if the :verbose flag is passed in.

Use reset to force a new discovery

[Source]

     # File lib/mcollective/rpc/client.rb, line 393
393:       def discover(flags={})
394:         flags.keys.each do |key|
395:           raise "Unknown option #{key} passed to discover" unless [:verbose, :hosts, :nodes, :json].include?(key)
396:         end
397: 
398:         flags.include?(:verbose) ? verbose = flags[:verbose] : verbose = @verbose
399: 
400:         verbose = false unless @output_format == :console
401: 
402:         # flags[:nodes] and flags[:hosts] are the same thing, we should never have
403:         # allowed :hosts as that was inconsistent with the established terminology
404:         flags[:nodes] = flags.delete(:hosts) if flags.include?(:hosts)
405: 
406:         reset if flags[:nodes] || flags[:json]
407: 
408:         unless @discovered_agents
409:           # if either hosts or JSON is supplied try to figure out discovery data from there
410:           # if direct_addressing is not enabled this is a critical error as the user might
411:           # not have supplied filters so raise an exception
412:           if flags[:nodes] || flags[:json]
413:             raise "Can only supply discovery data if direct_addressing is enabled" unless Config.instance.direct_addressing
414: 
415:             hosts = []
416: 
417:             if flags[:nodes]
418:               hosts = Helpers.extract_hosts_from_array(flags[:nodes])
419:             elsif flags[:json]
420:               hosts = Helpers.extract_hosts_from_json(flags[:json])
421:             end
422: 
423:             raise "Could not find any hosts in discovery data provided" if hosts.empty?
424: 
425:             @discovered_agents = hosts
426:             @force_direct_request = true
427: 
428:           # if an identity filter is supplied and it is all strings no regex we can use that
429:           # as discovery data, technically the identity filter is then redundant if we are
430:           # in direct addressing mode and we could empty it out but this use case should
431:           # only really be for a few -I's on the CLI
432:           #
433:           # For safety we leave the filter in place for now, that way we can support this
434:           # enhancement also in broadcast mode
435:           elsif options[:filter]["identity"].size > 0
436:             regex_filters = options[:filter]["identity"].select{|i| i.match("^\/")}.size
437: 
438:             if regex_filters == 0
439:               @discovered_agents = options[:filter]["identity"].clone
440:               @force_direct_request = true if Config.instance.direct_addressing
441:             end
442:           end
443:         end
444: 
445:         # All else fails we do it the hard way using a traditional broadcast
446:         unless @discovered_agents
447:           @stats.time_discovery :start
448: 
449:           @stderr.print("Determining the amount of hosts matching filter for #{discovery_timeout} seconds .... ") if verbose
450: 
451:           # if the requested limit is a pure number and not a percent
452:           # and if we're configured to use the first found hosts as the
453:           # limit method then pass in the limit thus minimizing the amount
454:           # of work we do in the discover phase and speeding it up significantly
455:           if @limit_method == :first and @limit_targets.is_a?(Fixnum)
456:             @discovered_agents = @client.discover(@filter, @discovery_timeout, @limit_targets)
457:           else
458:             @discovered_agents = @client.discover(@filter, @discovery_timeout)
459:           end
460: 
461:           @force_direct_request = false
462:           @stderr.puts(@discovered_agents.size) if verbose
463: 
464:           @stats.time_discovery :end
465:         end
466: 
467:         @stats.discovered_agents(@discovered_agents)
468:         RPC.discovered(@discovered_agents)
469: 
470:         @discovered_agents
471:       end

Sets the fact filter

[Source]

     # File lib/mcollective/rpc/client.rb, line 330
330:       def fact_filter(fact, value=nil, operator="=")
331:         return if fact.nil?
332:         return if fact == false
333: 
334:         if value.nil?
335:           parsed = Util.parse_fact_string(fact)
336:           @filter["fact"] << parsed unless parsed == false
337:         else
338:           parsed = Util.parse_fact_string("#{fact}#{operator}#{value}")
339:           @filter["fact"] << parsed unless parsed == false
340:         end
341: 
342:         @filter["fact"].compact!
343:         reset
344:       end

Returns help for an agent if a DDL was found

[Source]

     # File lib/mcollective/rpc/client.rb, line 112
112:       def help(template)
113:         if @ddl
114:           @ddl.help(template)
115:         else
116:           return "Can't find DDL for agent '#{@agent}'"
117:         end
118:       end

Sets the identity filter

[Source]

     # File lib/mcollective/rpc/client.rb, line 354
354:       def identity_filter(identity)
355:         @filter["identity"] << identity
356:         @filter["identity"].compact!
357:         reset
358:       end

Sets and sanity check the limit_method variable used to determine how to limit targets if limit_targets is set

[Source]

     # File lib/mcollective/rpc/client.rb, line 510
510:       def limit_method=(method)
511:         method = method.to_sym unless method.is_a?(Symbol)
512: 
513:         raise "Unknown limit method #{method} must be :random or :first" unless [:random, :first].include?(method)
514: 
515:         @limit_method = method
516:       end

Sets and sanity checks the limit_targets variable used to restrict how many nodes we‘ll target

[Source]

     # File lib/mcollective/rpc/client.rb, line 494
494:       def limit_targets=(limit)
495:         if limit.is_a?(String)
496:           raise "Invalid limit specified: #{limit} valid limits are /^\d+%*$/" unless limit =~ /^\d+%*$/
497: 
498:           begin
499:             @limit_targets = Integer(limit)
500:           rescue
501:             @limit_targets = limit
502:           end
503:         else
504:           @limit_targets = Integer(limit)
505:         end
506:       end

Magic handler to invoke remote methods

Once the stub is created using the constructor or the RPC#rpcclient helper you can call remote actions easily:

  ret = rpc.echo(:msg => "hello world")

This will call the ‘echo’ action of the ‘rpctest’ agent and return the result as an array, the array will be a simplified result set from the usual full MCollective::Client#req with additional error codes and error text:

{

  :sender => "remote.box.com",
  :statuscode => 0,
  :statusmsg => "OK",
  :data => "hello world"

}

If :statuscode is 0 then everything went find, if it‘s 1 then you supplied the correct arguments etc but the request could not be completed, you‘ll find a human parsable reason in :statusmsg then.

Codes 2 to 5 maps directly to UnknownRPCAction, MissingRPCData, InvalidRPCData and UnknownRPCError see below for a description of those, in each case :statusmsg would be the reason for failure.

To get access to the full result of the MCollective::Client#req calls you can pass in a block:

  rpc.echo(:msg => "hello world") do |resp|
     pp resp
  end

In this case resp will the result from MCollective::Client#req. Instead of returning simple text and codes as above you‘ll also need to handle the following exceptions:

UnknownRPCAction - There is no matching action on the agent MissingRPCData - You did not supply all the needed parameters for the action InvalidRPCData - The data you did supply did not pass validation UnknownRPCError - Some other error prevented the agent from running

During calls a progress indicator will be shown of how many results we‘ve received against how many nodes were discovered, you can disable this by setting progress to false:

  rpc.progress = false

This supports a 2nd mode where it will send the SimpleRPC request and never handle the responses. It‘s a bit like UDP, it sends the request with the filter attached and you only get back the requestid, you have no indication about results.

You can invoke this using:

  puts rpc.echo(:process_results => false)

This will output just the request id.

Batched processing is supported:

  printrpc rpc.ping(:batch_size => 5)

This will do everything exactly as normal but communicate to only 5 agents at a time

[Source]

     # File lib/mcollective/rpc/client.rb, line 209
209:       def method_missing(method_name, *args, &block)
210:         # set args to an empty hash if nothings given
211:         args = args[0]
212:         args = {} if args.nil?
213: 
214:         action = method_name.to_s
215: 
216:         @stats.reset
217: 
218:         @ddl.validate_request(action, args) if @ddl
219: 
220:         # if a global batch size is set just use that else set it
221:         # in the case that it was passed as an argument
222:         batch_mode = args.include?(:batch_size) || @batch_mode
223:         batch_size = args.delete(:batch_size) || @batch_size
224:         batch_sleep_time = args.delete(:batch_sleep_time) || @batch_sleep_time
225: 
226:         # if we were given a batch_size argument thats 0 and batch_mode was
227:         # determined to be on via global options etc this will allow a batch_size
228:         # of 0 to disable or batch_mode for this call only
229:         batch_mode = (batch_mode && Integer(batch_size) > 0)
230: 
231:         # Handle single target requests by doing discovery and picking
232:         # a random node.  Then do a custom request specifying a filter
233:         # that will only match the one node.
234:         if @limit_targets
235:           target_nodes = pick_nodes_from_discovered(@limit_targets)
236:           Log.debug("Picked #{target_nodes.join(',')} as limited target(s)")
237: 
238:           custom_request(action, args, target_nodes, {"identity" => /^(#{target_nodes.join('|')})$/}, &block)
239:         elsif batch_mode
240:           call_agent_batched(action, args, options, batch_size, batch_sleep_time, &block)
241:         else
242:           call_agent(action, args, options, :auto, &block)
243:         end
244:       end

Creates a suitable request hash for the SimpleRPC agent.

You‘d use this if you ever wanted to take care of sending requests on your own - perhaps via Client#sendreq if you didn‘t care for responses.

In that case you can just do:

  msg = your_rpc.new_request("some_action", :foo => :bar)
  filter = your_rpc.filter

  your_rpc.client.sendreq(msg, msg[:agent], filter)

This will send a SimpleRPC request to the action some_action with arguments :foo = :bar, it will return immediately and you will have no indication at all if the request was receieved or not

Clearly the use of this technique should be limited and done only if your code requires such a thing

[Source]

     # File lib/mcollective/rpc/client.rb, line 139
139:       def new_request(action, data)
140:         callerid = PluginManager["security_plugin"].callerid
141: 
142:         raise 'callerid received from security plugin is not valid' unless PluginManager["security_plugin"].valid_callerid?(callerid)
143: 
144:         {:agent  => @agent,
145:          :action => action,
146:          :caller => callerid,
147:          :data   => data}
148:       end

Provides a normal options hash like you would get from Optionparser

[Source]

     # File lib/mcollective/rpc/client.rb, line 475
475:       def options
476:         {:disctimeout => @discovery_timeout,
477:          :timeout => @timeout,
478:          :verbose => @verbose,
479:          :filter => @filter,
480:          :collective => @collective,
481:          :output_format => @output_format,
482:          :ttl => @ttl,
483:          :config => @config}
484:       end

Resets various internal parts of the class, most importantly it clears out the cached discovery

[Source]

     # File lib/mcollective/rpc/client.rb, line 368
368:       def reset
369:         @discovered_agents = nil
370:       end

Reet the filter to an empty one

[Source]

     # File lib/mcollective/rpc/client.rb, line 373
373:       def reset_filter
374:         @filter = Util.empty_filter
375:         agent_filter @agent
376:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RPC/Request.html0000644000175000017500000003275011747546503022422 0ustar jonasjonas Class: MCollective::RPC::Request
Class MCollective::RPC::Request
In: lib/mcollective/rpc/request.rb
Parent: Object

Simple class to manage compliant requests for MCollective::RPC agents

Methods

[]   include?   new   should_respond?   to_hash   to_json  

Attributes

action  [RW] 
agent  [RW] 
caller  [RW] 
data  [RW] 
sender  [RW] 
time  [RW] 
uniqid  [RW] 

Public Class methods

[Source]

    # File lib/mcollective/rpc/request.rb, line 7
 7:       def initialize(msg)
 8:         @time = msg[:msgtime]
 9:         @action = msg[:body][:action]
10:         @data = msg[:body][:data]
11:         @sender = msg[:senderid]
12:         @agent = msg[:body][:agent]
13:         @uniqid = msg[:requestid]
14:         @caller = msg[:callerid] || "unknown"
15:       end

Public Instance methods

If data is a hash, gives easy access to its members, else returns nil

[Source]

    # File lib/mcollective/rpc/request.rb, line 33
33:       def [](key)
34:         return nil unless @data.is_a?(Hash)
35:         return @data[key]
36:       end

If data is a hash, quick helper to get access to it‘s include? method else returns false

[Source]

    # File lib/mcollective/rpc/request.rb, line 19
19:       def include?(key)
20:         return false unless @data.is_a?(Hash)
21:         return @data.include?(key)
22:       end

If no :process_results is specified always respond else respond based on the supplied property

[Source]

    # File lib/mcollective/rpc/request.rb, line 26
26:       def should_respond?
27:         return @data[:process_results] if @data.include?(:process_results)
28: 
29:         return true
30:       end

[Source]

    # File lib/mcollective/rpc/request.rb, line 38
38:       def to_hash
39:         return {:agent => @agent,
40:           :action => @action,
41:           :data => @data}
42:       end

[Source]

    # File lib/mcollective/rpc/request.rb, line 44
44:       def to_json
45:         to_hash.merge!({:sender   => @sender,
46:                          :callerid => @callerid,
47:                          :uniqid   => @uniqid}).to_json
48:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RPC/Helpers.html0000644000175000017500000015135211747546503022374 0ustar jonasjonas Class: MCollective::RPC::Helpers
Class MCollective::RPC::Helpers
In: lib/mcollective/rpc/helpers.rb
Parent: Object

Various utilities for the RPC system

Methods

Public Class methods

Add SimpleRPC common options

[Source]

     # File lib/mcollective/rpc/helpers.rb, line 302
302:       def self.add_simplerpc_options(parser, options)
303:         parser.separator ""
304: 
305:         # add SimpleRPC specific options to all clients that use our library
306:         parser.on('--np', '--no-progress', 'Do not show the progress bar') do |v|
307:           options[:progress_bar] = false
308:         end
309: 
310:         parser.on('--one', '-1', 'Send request to only one discovered nodes') do |v|
311:           options[:mcollective_limit_targets] = 1
312:         end
313: 
314:         parser.on('--batch SIZE', Integer, 'Do requests in batches') do |v|
315:           options[:batch_size] = v
316:         end
317: 
318:         parser.on('--batch-sleep SECONDS', Float, 'Sleep time between batches') do |v|
319:           options[:batch_sleep_time] = v
320:         end
321: 
322:         parser.on('--limit-nodes COUNT', '--ln', 'Send request to only a subset of nodes, can be a percentage') do |v|
323:           raise "Invalid limit specified: #{v} valid limits are /^\d+%*$/" unless v =~ /^\d+%*$/
324: 
325:           if v =~ /^\d+$/
326:             options[:mcollective_limit_targets] = v.to_i
327:           else
328:             options[:mcollective_limit_targets] = v
329:           end
330:         end
331: 
332:         parser.on('--json', '-j', 'Produce JSON output') do |v|
333:           options[:progress_bar] = false
334:           options[:output_format] = :json
335:         end
336:       end

Return color codes, if the config color= option is false just return a empty string

[Source]

    # File lib/mcollective/rpc/helpers.rb, line 71
71:       def self.color(code)
72:         colorize = Config.instance.color
73: 
74:         colors = {:red => "",
75:           :green => "",
76:           :yellow => "",
77:           :cyan => "",
78:           :bold => "",
79:           :reset => ""}
80: 
81:         if colorize
82:           return colors[code] || ""
83:         else
84:           return ""
85:         end
86:       end

Helper to return a string in specific color

[Source]

    # File lib/mcollective/rpc/helpers.rb, line 89
89:       def self.colorize(code, msg)
90:         "#{self.color(code)}#{msg}#{self.color(:reset)}"
91:       end

Checks in PATH returns true if the command is found

[Source]

    # File lib/mcollective/rpc/helpers.rb, line 6
 6:       def self.command_in_path?(command)
 7:         found = ENV["PATH"].split(File::PATH_SEPARATOR).map do |p|
 8:           File.exist?(File.join(p, command))
 9:         end
10: 
11:         found.include?(true)
12:       end

Given an array of something, make sure each is a string chomp off any new lines and return just the array of hosts

[Source]

    # File lib/mcollective/rpc/helpers.rb, line 38
38:       def self.extract_hosts_from_array(hosts)
39:         [hosts].flatten.map do |host|
40:           raise "#{host} should be a string" unless host.is_a?(String)
41:           host.chomp
42:         end
43:       end

Parse JSON output as produced by printrpc and extract the "sender" of each rpc response

The simplist valid JSON based data would be:

[

 {"sender" => "example.com"},
 {"sender" => "another.com"}

]

[Source]

    # File lib/mcollective/rpc/helpers.rb, line 23
23:       def self.extract_hosts_from_json(json)
24:         hosts = JSON.parse(json)
25: 
26:         raise "JSON hosts list is not an array" unless hosts.is_a?(Array)
27: 
28:         hosts.map do |host|
29:           raise "JSON host list is not an array of Hashes" unless host.is_a?(Hash)
30:           raise "JSON host list does not have senders in it" unless host.include?("sender")
31: 
32:           host["sender"]
33:         end.uniq
34:       end

Backward compatible display block for results without a DDL

[Source]

     # File lib/mcollective/rpc/helpers.rb, line 250
250:       def self.old_rpcresults(result, flags = {})
251:         result_text = ""
252: 
253:         if flags[:flatten]
254:           result.each do |r|
255:             if r[:statuscode] <= 1
256:               data = r[:data]
257: 
258:               unless data.is_a?(String)
259:                 result_text << data.pretty_inspect
260:               else
261:                 result_text << data
262:               end
263:             else
264:               result_text << r.pretty_inspect
265:             end
266:           end
267: 
268:           result_text << ""
269:         else
270:           [result].flatten.each do |r|
271: 
272:             if flags[:verbose]
273:               result_text << "%-40s: %s\n" % [r[:sender], r[:statusmsg]]
274: 
275:               if r[:statuscode] <= 1
276:                 r[:data].pretty_inspect.split("\n").each {|m| result_text += "    #{m}"}
277:                 result_text << "\n\n"
278:               elsif r[:statuscode] == 2
279:                 # dont print anything, no useful data to display
280:                 # past what was already shown
281:               elsif r[:statuscode] == 3
282:                 # dont print anything, no useful data to display
283:                 # past what was already shown
284:               elsif r[:statuscode] == 4
285:                 # dont print anything, no useful data to display
286:                 # past what was already shown
287:               else
288:                 result_text << "    #{r[:statusmsg]}"
289:               end
290:             else
291:               unless r[:statuscode] == 0
292:                 result_text << "%-40s %s\n" % [r[:sender], colorize(:red, r[:statusmsg])]
293:               end
294:             end
295:           end
296:         end
297: 
298:         result_text << ""
299:       end

Returns a blob of text representing the results in a standard way

It tries hard to do sane things so you often should not need to write your own display functions

If the agent you are getting results for has a DDL it will use the hints in there to do the right thing specifically it will look at the values of display in the DDL to choose when to show results

If you do not have a DDL you can pass these flags:

   printrpc exim.mailq, :flatten => true
   printrpc exim.mailq, :verbose => true

If you‘ve asked it to flatten the result it will not print sender hostnames, it will just print the result as if it‘s one huge result, handy for things like showing a combined mailq.

[Source]

     # File lib/mcollective/rpc/helpers.rb, line 111
111:       def self.rpcresults(result, flags = {})
112:         flags = {:verbose => false, :flatten => false, :format => :console}.merge(flags)
113: 
114:         result_text = ""
115:         ddl = nil
116: 
117:         # if running in verbose mode, just use the old style print
118:         # no need for all the DDL helpers obfuscating the result
119:         if flags[:format] == :json
120:           if STDOUT.tty?
121:             result_text = JSON.pretty_generate(result)
122:           else
123:             result_text = result.to_json
124:           end
125:         else
126:           if flags[:verbose]
127:             result_text = old_rpcresults(result, flags)
128:           else
129:             [result].flatten.each do |r|
130:               begin
131:                 ddl ||= DDL.new(r.agent).action_interface(r.action.to_s)
132: 
133:                 sender = r[:sender]
134:                 status = r[:statuscode]
135:                 message = r[:statusmsg]
136:                 display = ddl[:display]
137:                 result = r[:data]
138: 
139:                 # appand the results only according to what the DDL says
140:                 case display
141:                 when :ok
142:                   if status == 0
143:                     result_text << text_for_result(sender, status, message, result, ddl)
144:                   end
145: 
146:                 when :failed
147:                   if status > 0
148:                     result_text << text_for_result(sender, status, message, result, ddl)
149:                   end
150: 
151:                 when :always
152:                   result_text << text_for_result(sender, status, message, result, ddl)
153: 
154:                 when :flatten
155:                   result_text << text_for_flattened_result(status, result)
156: 
157:                 end
158:               rescue Exception => e
159:                 # no DDL so just do the old style print unchanged for
160:                 # backward compat
161:                 result_text = old_rpcresults(result, flags)
162:               end
163:             end
164:           end
165:         end
166: 
167:         result_text
168:       end

Figures out the columns and liens of the current tty

Returns [0, 0] if it can‘t figure it out or if you‘re not running on a tty

[Source]

    # File lib/mcollective/rpc/helpers.rb, line 49
49:       def self.terminal_dimensions
50:         return [0, 0] unless STDOUT.tty?
51: 
52:         return [80, 40] if Util.windows?
53: 
54:         if ENV["COLUMNS"] && ENV["LINES"]
55:           return [ENV["COLUMNS"].to_i, ENV["LINES"].to_i]
56: 
57:         elsif ENV["TERM"] && command_in_path?("tput")
58:           return [`tput cols`.to_i, `tput lines`.to_i]
59: 
60:         elsif command_in_path?('stty')
61:           return `stty size`.scan(/\d+/).map {|s| s.to_i }
62:         else
63:           return [0, 0]
64:         end
65:       rescue
66:         [0, 0]
67:       end

Returns text representing a flattened result of only good data

[Source]

     # File lib/mcollective/rpc/helpers.rb, line 237
237:       def self.text_for_flattened_result(status, result)
238:         result_text = ""
239: 
240:         if status <= 1
241:           unless result.is_a?(String)
242:             result_text << result.pretty_inspect
243:           else
244:             result_text << result
245:           end
246:         end
247:       end

Return text representing a result

[Source]

     # File lib/mcollective/rpc/helpers.rb, line 171
171:       def self.text_for_result(sender, status, msg, result, ddl)
172:         statusses = ["",
173:                      colorize(:red, "Request Aborted"),
174:                      colorize(:yellow, "Unknown Action"),
175:                      colorize(:yellow, "Missing Request Data"),
176:                      colorize(:yellow, "Invalid Request Data"),
177:                      colorize(:red, "Unknown Request Status")]
178: 
179:         result_text = "%-40s %s\n" % [sender, statusses[status]]
180:         result_text << "   %s\n" % [colorize(:yellow, msg)] unless msg == "OK"
181: 
182:         # only print good data, ignore data that results from failure
183:         if [0, 1].include?(status)
184:           if result.is_a?(Hash)
185:             # figure out the lengths of the display as strings, we'll use
186:             # it later to correctly justify the output
187:             lengths = result.keys.map do |k|
188:               begin
189:                 ddl[:output][k][:display_as].size
190:               rescue
191:                 k.to_s.size
192:               end
193:             end
194: 
195:             result.keys.each do |k|
196:               # get all the output fields nicely lined up with a
197:               # 3 space front padding
198:               begin
199:                 display_as = ddl[:output][k][:display_as]
200:               rescue
201:                 display_as = k.to_s
202:               end
203: 
204:               display_length = display_as.size
205:               padding = lengths.max - display_length + 3
206:               result_text << " " * padding
207: 
208:               result_text << "#{display_as}:"
209: 
210:               if [String, Numeric].include?(result[k].class)
211:                 lines = result[k].to_s.split("\n")
212: 
213:                 if lines.empty?
214:                   result_text << "\n"
215:                 else
216:                   lines.each_with_index do |line, i|
217:                     i == 0 ? padtxt = " " : padtxt = " " * (padding + display_length + 2)
218: 
219:                     result_text << "#{padtxt}#{line}\n"
220:                   end
221:                 end
222:               else
223:                 padding = " " * (lengths.max + 5)
224:                 result_text << " " << result[k].pretty_inspect.split("\n").join("\n" << padding) << "\n"
225:               end
226:             end
227:           else
228:             result_text << "\n\t" + result.pretty_inspect.split("\n").join("\n\t")
229:           end
230:         end
231: 
232:         result_text << "\n"
233:         result_text
234:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RPC/Progress.html0000644000175000017500000002372711747546503022602 0ustar jonasjonas Class: MCollective::RPC::Progress
Class MCollective::RPC::Progress
In: lib/mcollective/rpc/progress.rb
Parent: Object

Class that shows a progress bar, currently only supports a twirling progress bar.

You can specify a size for the progress bar if you want if you dont it will use the helper functions to figure out terminal dimensions and draw an appropriately sized bar

p = Progress.new 100.times {|i| print p.twirl(i+1, 100) + "\r"};puts

 * [ ==================================================> ] 100 / 100

Methods

new   twirl  

Public Class methods

[Source]

    # File lib/mcollective/rpc/progress.rb, line 15
15:       def initialize(size=nil)
16:         @twirl = ['|', '/', '-', "\\", '|', '/', '-', "\\"]
17:         @twirldex = 0
18: 
19:         if size
20:           @size = size
21:         else
22:           cols = Helpers.terminal_dimensions[0] - 22
23: 
24:           # Defaults back to old behavior if it
25:           # couldn't figure out the size or if
26:           # its more than 60 wide
27:           if cols <= 0
28:             @size = 0
29:           elsif cols > 60
30:             @size = 60
31:           else
32:             @size = cols
33:           end
34:         end
35:       end

Public Instance methods

[Source]

    # File lib/mcollective/rpc/progress.rb, line 37
37:       def twirl(current, total)
38:         # if the size is negative there is just not enough
39:         # space on the terminal, return a simpler version
40:         return "\r#{current} / #{total}" if @size == 0
41: 
42:         if current == total
43:           txt = "\r " + Helpers.colorize(:green, "*") + " [ "
44:         else
45:           txt = "\r #{@twirl[@twirldex]} [ "
46:         end
47: 
48:         dashes = ((current.to_f / total) * @size).round
49: 
50:         dashes.times { txt << "=" }
51:         txt << ">"
52: 
53:         (@size - dashes).times { txt << " " }
54: 
55:         txt << " ] #{current} / #{total}"
56: 
57:         @twirldex == 7 ? @twirldex = 0 : @twirldex += 1
58: 
59:         return txt
60:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RPC/DDL.html0000644000175000017500000011657011747546503021400 0ustar jonasjonas Class: MCollective::RPC::DDL
Class MCollective::RPC::DDL
In: lib/mcollective/rpc/ddl.rb
Parent: Object

A class that helps creating data description language files for agents. You can define meta data, actions, input and output describing the behavior of your agent.

Later you can access this information to assist with creating of user interfaces or online help

A sample DDL can be seen below, you‘d put this in your agent dir as <agent name>.ddl

   metadata :name        => "SimpleRPC Service Agent",
            :description => "Agent to manage services using the Puppet service provider",
            :author      => "R.I.Pienaar",
            :license     => "GPLv2",
            :version     => "1.1",
            :url         => "http://mcollective-plugins.googlecode.com/",
            :timeout     => 60

   action "status", :description => "Gets the status of a service" do
      display :always

      input "service",
            :prompt      => "Service Name",
            :description => "The service to get the status for",
            :type        => :string,
            :validation  => '^[a-zA-Z\-_\d]+$',
            :optional    => true,
            :maxlength   => 30

      output "status",
            :description => "The status of service",
            :display_as  => "Service Status"
  end

Methods

Attributes

meta  [R] 

Public Class methods

[Source]

    # File lib/mcollective/rpc/ddl.rb, line 39
39:       def initialize(agent, loadddl=true)
40:         @actions = {}
41:         @meta = {}
42:         @config = MCollective::Config.instance
43:         @agent = agent
44: 
45:         if loadddl
46:           if ddlfile = findddlfile(agent)
47:             instance_eval(File.read(ddlfile))
48:           else
49:             raise("Can't find DDL for agent '#{agent}'")
50:           end
51:         end
52:       end

Public Instance methods

Creates the definition for an action, you can nest input definitions inside the action to attach inputs and validation to the actions

   action "status", :description => "Restarts a Service" do
      display :always

      input "service",
           :prompt      => "Service Action",
           :description => "The action to perform",
           :type        => :list,
           :optional    => true,
           :list        => ["start", "stop", "restart", "status"]

      output "status"
           :description => "The status of the service after the action"

   end

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 92
 92:       def action(name, input, &block)
 93:         raise "Action needs a :description" unless input.include?(:description)
 94: 
 95:         unless @actions.include?(name)
 96:           @actions[name] = {}
 97:           @actions[name][:action] = name
 98:           @actions[name][:input] = {}
 99:           @actions[name][:output] = {}
100:           @actions[name][:display] = :failed
101:           @actions[name][:description] = input[:description]
102:         end
103: 
104:         # if a block is passed it might be creating input methods, call it
105:         # we set @current_action so the input block can know what its talking
106:         # to, this is probably an epic hack, need to improve.
107:         @current_action = name
108:         block.call if block_given?
109:         @current_action = nil
110:       end

Returns the interface for a specific action

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 187
187:       def action_interface(name)
188:         @actions[name] || {}
189:       end

Returns an array of actions this agent support

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 182
182:       def actions
183:         @actions.keys
184:       end

Sets the display preference to either :ok, :failed, :flatten or :always operates on action level

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 160
160:       def display(pref)
161:         # defaults to old behavior, complain if its supplied and invalid
162:         unless [:ok, :failed, :flatten, :always].include?(pref)
163:           raise "Display preference #{pref} is not valid, should be :ok, :failed, :flatten or :always"
164:         end
165: 
166:         action = @current_action
167:         @actions[action][:display] = pref
168:       end

[Source]

    # File lib/mcollective/rpc/ddl.rb, line 54
54:       def findddlfile(agent)
55:         @config.libdir.each do |libdir|
56:           ddlfile = File.join([libdir, "mcollective", "agent", "#{agent}.ddl"])
57: 
58:           if File.exist?(ddlfile)
59:             Log.debug("Found #{agent} ddl at #{ddlfile}")
60:             return ddlfile
61:           end
62:         end
63:         return false
64:       end

Generates help using the template based on the data created with metadata and input

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 172
172:       def help(template)
173:         template = IO.read(template)
174:         meta = @meta
175:         actions = @actions
176: 
177:         erb = ERB.new(template, 0, '%')
178:         erb.result(binding)
179:       end

Registers an input argument for a given action

See the documentation for action for how to use this

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 115
115:       def input(argument, properties)
116:         raise "Cannot figure out what action input #{argument} belongs to" unless @current_action
117: 
118:         action = @current_action
119: 
120:         [:prompt, :description, :type, :optional].each do |arg|
121:           raise "Input needs a :#{arg}" unless properties.include?(arg)
122:         end
123: 
124:         @actions[action][:input][argument] = {:prompt => properties[:prompt],
125:                                               :description => properties[:description],
126:                                               :type => properties[:type],
127:                                               :optional => properties[:optional]}
128: 
129:         case properties[:type]
130:           when :string
131:             raise "Input type :string needs a :validation argument" unless properties.include?(:validation)
132:             raise "Input type :string needs a :maxlength argument" unless properties.include?(:maxlength)
133: 
134:             @actions[action][:input][argument][:validation] = properties[:validation]
135:             @actions[action][:input][argument][:maxlength] = properties[:maxlength]
136: 
137:           when :list
138:             raise "Input type :list needs a :list argument" unless properties.include?(:list)
139: 
140:             @actions[action][:input][argument][:list] = properties[:list]
141:         end
142:       end

Registers meta data for the introspection hash

[Source]

    # File lib/mcollective/rpc/ddl.rb, line 67
67:       def metadata(meta)
68:         [:name, :description, :author, :license, :version, :url, :timeout].each do |arg|
69:           raise "Metadata needs a :#{arg}" unless meta.include?(arg)
70:         end
71: 
72:         @meta = meta
73:       end

Registers an output argument for a given action

See the documentation for action for how to use this

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 147
147:       def output(argument, properties)
148:         raise "Cannot figure out what action input #{argument} belongs to" unless @current_action
149:         raise "Output #{argument} needs a description argument" unless properties.include?(:description)
150:         raise "Output #{argument} needs a display_as argument" unless properties.include?(:display_as)
151: 
152:         action = @current_action
153: 
154:         @actions[action][:output][argument] = {:description => properties[:description],
155:                                                :display_as  => properties[:display_as]}
156:       end

Helper to use the DDL to figure out if the remote call should be allowed based on action name and inputs.

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 193
193:       def validate_request(action, arguments)
194:         # is the action known?
195:         unless actions.include?(action)
196:           raise DDLValidationError, "Attempted to call action #{action} for #{@agent} but it's not declared in the DDL"
197:         end
198: 
199:         input = action_interface(action)[:input]
200: 
201:         input.keys.each do |key|
202:           unless input[key][:optional]
203:             unless arguments.keys.include?(key)
204:               raise DDLValidationError, "Action #{action} needs a #{key} argument"
205:             end
206:           end
207: 
208:           # validate strings, lists and booleans, we'll add more types of validators when
209:           # all the use cases are clear
210:           #
211:           # only does validation for arguments actually given, since some might
212:           # be optional.  We validate the presense of the argument earlier so
213:           # this is a safe assumption, just to skip them.
214:           #
215:           # :string can have maxlength and regex.  A maxlength of 0 will bypasss checks
216:           # :list has a array of valid values
217:           if arguments.keys.include?(key)
218:             case input[key][:type]
219:               when :string
220:                 raise DDLValidationError, "Input #{key} should be a string" unless arguments[key].is_a?(String)
221: 
222:                 if input[key][:maxlength].to_i > 0
223:                   if arguments[key].size > input[key][:maxlength].to_i
224:                     raise DDLValidationError, "Input #{key} is longer than #{input[key][:maxlength]} character(s)"
225:                   end
226:                 end
227: 
228:                 unless arguments[key].match(Regexp.new(input[key][:validation]))
229:                   raise DDLValidationError, "Input #{key} does not match validation regex #{input[key][:validation]}"
230:                 end
231: 
232:               when :list
233:                 unless input[key][:list].include?(arguments[key])
234:                   raise DDLValidationError, "Input #{key} doesn't match list #{input[key][:list].join(', ')}"
235:                 end
236: 
237:               when :boolean
238:                 unless [TrueClass, FalseClass].include?(arguments[key].class)
239:                   raise DDLValidationError, "Input #{key} should be a boolean"
240:                 end
241: 
242:               when :integer
243:                 raise DDLValidationError, "Input #{key} should be a integer" unless arguments[key].is_a?(Fixnum)
244: 
245:               when :float
246:                 raise DDLValidationError, "Input #{key} should be a floating point number" unless arguments[key].is_a?(Float)
247: 
248:               when :number
249:                 raise DDLValidationError, "Input #{key} should be a number" unless arguments[key].is_a?(Numeric)
250:             end
251:           end
252:         end
253: 
254:         true
255:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RPC/Stats.html0000644000175000017500000011037611747546503022071 0ustar jonasjonas Class: MCollective::RPC::Stats
Class MCollective::RPC::Stats
In: lib/mcollective/rpc/stats.rb
Parent: Object

Class to wrap all the stats and to keep track of some timings

Methods

Attributes

blocktime  [RW] 
discovered  [RW] 
discovered_nodes  [RW] 
discoverytime  [RW] 
failcount  [RW] 
noresponsefrom  [RW] 
noresponsefrom  [RW] 
okcount  [RW] 
responses  [RW] 
responsesfrom  [RW] 
starttime  [RW] 
totaltime  [RW] 

Public Class methods

[Source]

    # File lib/mcollective/rpc/stats.rb, line 8
 8:       def initialize
 9:         reset
10:       end

Public Instance methods

Fake hash access to keep things backward compatible

[Source]

    # File lib/mcollective/rpc/stats.rb, line 44
44:       def [](key)
45:         to_hash[key]
46:       rescue
47:         nil
48:       end

Re-initializes the object with stats from the basic client

[Source]

    # File lib/mcollective/rpc/stats.rb, line 65
65:       def client_stats=(stats)
66:         @noresponsefrom = stats[:noresponsefrom]
67:         @responses = stats[:responses]
68:         @starttime = stats[:starttime]
69:         @blocktime = stats[:blocktime]
70:         @totaltime = stats[:totaltime]
71:         @discoverytime = stats[:discoverytime] if @discoverytime == 0
72:       end

Update discovered and discovered_nodes based on discovery results

[Source]

     # File lib/mcollective/rpc/stats.rb, line 102
102:       def discovered_agents(agents)
103:         @discovered_nodes = agents
104:         @discovered = agents.size
105:       end

increment the count of failed hosts

[Source]

    # File lib/mcollective/rpc/stats.rb, line 58
58:       def fail
59:         @failcount += 1
60:       rescue
61:         @failcount = 1
62:       end

Helper to calculate total time etc

[Source]

     # File lib/mcollective/rpc/stats.rb, line 108
108:       def finish_request
109:         @totaltime = @blocktime + @discoverytime
110: 
111:         # figures out who we had no responses from
112:         dhosts = @discovered_nodes.clone
113:         @responsesfrom.each {|r| dhosts.delete(r)}
114:         @noresponsefrom = dhosts
115:       rescue
116:         @totaltime = 0
117:         @noresponsefrom = []
118:       end

Returns a blob of text indicating what nodes did not respond

[Source]

     # File lib/mcollective/rpc/stats.rb, line 167
167:       def no_response_report
168:         result_text = []
169: 
170:         if @noresponsefrom.size > 0
171:           result_text << Helpers.colorize(:red, "\nNo response from:\n")
172: 
173:           @noresponsefrom.each_with_index do |c,i|
174:             result_text << "" if i % 4 == 0
175:             result_text << "%30s" % [c]
176:           end
177: 
178:           result_text << ""
179:         end
180: 
181:         result_text.join("\n")
182:       end

Helper to keep track of who we received responses from

[Source]

     # File lib/mcollective/rpc/stats.rb, line 121
121:       def node_responded(node)
122:         @responsesfrom << node
123:       rescue
124:         @responsesfrom = [node]
125:       end

increment the count of ok hosts

[Source]

    # File lib/mcollective/rpc/stats.rb, line 51
51:       def ok
52:         @okcount += 1
53:       rescue
54:         @okcount = 1
55:       end

Returns a blob of text representing the request status based on the stats contained in this class

[Source]

     # File lib/mcollective/rpc/stats.rb, line 129
129:       def report(caption = "rpc stats", verbose = false)
130:         result_text = []
131: 
132:         if verbose
133:           result_text << Helpers.colorize(:yellow, "---- #{caption} ----")
134: 
135:           if @discovered
136:             @responses < @discovered ? color = :red : color = :reset
137:             result_text << "           Nodes: %s / %s" % [ Helpers.colorize(color, @discovered), Helpers.colorize(color, @responses) ]
138:           else
139:             result_text << "           Nodes: #{@responses}"
140:           end
141: 
142:           @failcount < 0 ? color = :red : color = :reset
143: 
144:           result_text << "     Pass / Fail: %s / %s" % [Helpers.colorize(color, @okcount), Helpers.colorize(color, @failcount) ]
145:           result_text << "      Start Time: %s"      % [Time.at(@starttime)]
146:           result_text << "  Discovery Time: %.2fms"  % [@discoverytime * 1000]
147:           result_text << "      Agent Time: %.2fms"  % [@blocktime * 1000]
148:           result_text << "      Total Time: %.2fms"  % [@totaltime * 1000]
149:         else
150:           if @discovered
151:             @responses < @discovered ? color = :red : color = :green
152: 
153:             result_text << "Finished processing %s / %s hosts in %.2f ms" % [Helpers.colorize(color, @responses), Helpers.colorize(color, @discovered), @blocktime * 1000]
154:           else
155:             result_text << "Finished processing %s hosts in %.2f ms" % [Helpers.colorize(:bold, @responses), @blocktime * 1000]
156:           end
157:         end
158: 
159:         if no_response_report != ""
160:           result_text << "" << no_response_report
161:         end
162: 
163:         result_text.join("\n")
164:       end

Resets stats, if discovery time is set we keep it as it was

[Source]

    # File lib/mcollective/rpc/stats.rb, line 13
13:       def reset
14:         @noresponsefrom = []
15:         @responsesfrom = []
16:         @responses = 0
17:         @starttime = Time.now.to_f
18:         @discoverytime = 0 unless @discoverytime
19:         @blocktime = 0
20:         @totaltime = 0
21:         @discovered = 0
22:         @discovered_nodes = []
23:         @okcount = 0
24:         @failcount = 0
25:         @noresponsefrom = []
26:       end

helper to time block execution time

[Source]

    # File lib/mcollective/rpc/stats.rb, line 88
88:       def time_block_execution(action)
89:         if action == :start
90:           @block_start = Time.now.to_f
91:         elsif action == :end
92:           @blocktime += Time.now.to_f - @block_start
93:         else
94:           raise("Uknown block action #{action}")
95:         end
96:       rescue
97:         @blocktime = 0
98:       end

Utility to time discovery from :start to :end

[Source]

    # File lib/mcollective/rpc/stats.rb, line 75
75:       def time_discovery(action)
76:         if action == :start
77:           @discovery_start = Time.now.to_f
78:         elsif action == :end
79:           @discoverytime = Time.now.to_f - @discovery_start
80:         else
81:           raise("Uknown discovery action #{action}")
82:         end
83:       rescue
84:         @discoverytime = 0
85:       end

returns a hash of our stats

[Source]

    # File lib/mcollective/rpc/stats.rb, line 29
29:       def to_hash
30:         {:noresponsefrom   => @noresponsefrom,
31:          :starttime        => @starttime,
32:          :discoverytime    => @discoverytime,
33:          :blocktime        => @blocktime,
34:          :responses        => @responses,
35:          :totaltime        => @totaltime,
36:          :discovered       => @discovered,
37:          :discovered_nodes => @discovered_nodes,
38:          :noresponsefrom   => @noresponsefrom,
39:          :okcount          => @okcount,
40:          :failcount        => @failcount}
41:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Security.html0000644000175000017500000000704111747546503022150 0ustar jonasjonas Module: MCollective::Security
Module MCollective::Security
In: lib/mcollective/security/base.rb
lib/mcollective/security.rb

Security is implimented using a module structure and installations can configure which module they want to use.

Security modules deal with various aspects of authentication and authorization:

  • Determines if a filter excludes this host from dealing with a request
  • Serialization and Deserialization of messages
  • Validation of messages against keys, certificates or whatever the class choose to impliment
  • Encoding and Decoding of messages

To impliment a new security class using SSL for example you would inherit from the base class and only impliment:

  • decodemsg
  • encodereply
  • encoderequest
  • validrequest?

Each of these methods should increment various stats counters, see the default MCollective::Security::Psk module for examples of this

Filtering can be extended by providing a new validate_filter? method.

Classes and Modules

Class MCollective::Security::Base

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Shell.html0000644000175000017500000003333211747546503021412 0ustar jonasjonas Class: MCollective::Shell
Class MCollective::Shell
In: lib/mcollective/shell.rb
Parent: Object

Wrapper around systemu that handles executing of system commands in a way that makes stdout, stderr and status available. Supports timeouts and sets a default sane environment.

  s = Shell.new("date", opts)
  s.runcommand
  puts s.stdout
  puts s.stderr
  puts s.status.exitcode

Options hash can have:

  cwd         - the working directory the command will be run from
  stdin       - a string that will be sent to stdin of the program
  stdout      - a variable that will receive stdout, must support <<
  stderr      - a variable that will receive stdin, must support <<
  environment - the shell environment, defaults to include LC_ALL=C
                set to nil to clear the environment even of LC_ALL

Methods

new   runcommand  

Attributes

command  [R] 
cwd  [R] 
environment  [R] 
status  [R] 
stderr  [R] 
stdin  [R] 
stdout  [R] 

Public Class methods

[Source]

    # File lib/mcollective/shell.rb, line 24
24:     def initialize(command, options={})
25:       @environment = {"LC_ALL" => "C"}
26:       @command = command
27:       @status = nil
28:       @stdout = ""
29:       @stderr = ""
30:       @stdin = nil
31:       @cwd = Dir.tmpdir
32: 
33:       options.each do |opt, val|
34:         case opt.to_s
35:           when "stdout"
36:             raise "stdout should support <<" unless val.respond_to?("<<")
37:             @stdout = val
38: 
39:           when "stderr"
40:             raise "stderr should support <<" unless val.respond_to?("<<")
41:             @stderr = val
42: 
43:           when "stdin"
44:             raise "stdin should be a String" unless val.is_a?(String)
45:             @stdin = val
46: 
47:           when "cwd"
48:             raise "Directory #{val} does not exist" unless File.directory?(val)
49:             @cwd = val
50: 
51:           when "environment"
52:             if val.nil?
53:               @environment = {}
54:             else
55:               @environment.merge!(val.dup)
56:             end
57:         end
58:       end
59:     end

Public Instance methods

Actually does the systemu call passing in the correct environment, stdout and stderr

[Source]

    # File lib/mcollective/shell.rb, line 62
62:     def runcommand
63:       opts = {"env"    => @environment,
64:               "stdout" => @stdout,
65:               "stderr" => @stderr,
66:               "cwd"    => @cwd}
67: 
68:       opts["stdin"] = @stdin if @stdin
69: 
70:       # Running waitpid on the cid here will start a thread
71:       # with the waitpid in it, this way even if the thread
72:       # that started this process gets killed due to agent
73:       # timeout or such there will still be a waitpid waiting
74:       # for the child to exit and not leave zombies.
75:       @status = systemu(@command, opts) do |cid|
76:         begin
77:           sleep 1
78:           Process::waitpid(cid)
79:         rescue SystemExit
80:         rescue Errno::ECHILD
81:         rescue Exception => e
82:           Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}")
83:         end
84:       end
85:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Facts/0000755000175000017500000000000011747546503020511 5ustar jonasjonasmcollective-2.0.0/doc/classes/MCollective/Facts/Base.html0000644000175000017500000003525011747546503022256 0ustar jonasjonas Class: MCollective::Facts::Base
Class MCollective::Facts::Base
In: lib/mcollective/facts/base.rb
Parent: Object

A base class for fact providers, to make a new fully functional fact provider inherit from this and simply provide a self.get_facts method that returns a hash like:

 {"foo" => "bar",
  "bar" => "baz"}

Methods

Public Class methods

Registers new fact sources into the plugin manager

[Source]

    # File lib/mcollective/facts/base.rb, line 17
17:       def self.inherited(klass)
18:         PluginManager << {:type => "facts_plugin", :class => klass.to_s}
19:       end

[Source]

    # File lib/mcollective/facts/base.rb, line 10
10:       def initialize
11:         @facts = {}
12:         @last_good_facts = {}
13:         @last_facts_load = 0
14:       end

Public Instance methods

Plugins can override this to provide forced fact invalidation

[Source]

    # File lib/mcollective/facts/base.rb, line 81
81:       def force_reload?
82:         false
83:       end

Returns the value of a single fact

[Source]

    # File lib/mcollective/facts/base.rb, line 22
22:       def get_fact(fact=nil)
23:         config = Config.instance
24: 
25:         cache_time = config.fact_cache_time || 300
26: 
27:         Thread.exclusive do
28:           begin
29:             if (Time.now.to_i - @last_facts_load > cache_time.to_i ) || force_reload?
30:               Log.debug("Resetting facter cache, now: #{Time.now.to_i} last-known-good: #{@last_facts_load}")
31: 
32:               tfacts = load_facts_from_source
33: 
34:               # Force reset to last known good state on empty facts
35:               raise "Got empty facts" if tfacts.empty?
36: 
37:               @facts.clear
38: 
39:               tfacts.each_pair do |key,value|
40:                 @facts[key.to_s] = value.to_s
41:               end
42: 
43:               @last_good_facts = @facts.clone
44:               @last_facts_load = Time.now.to_i
45:             else
46:               Log.debug("Using cached facts now: #{Time.now.to_i} last-known-good: #{@last_facts_load}")
47:             end
48:           rescue Exception => e
49:             Log.error("Failed to load facts: #{e.class}: #{e}")
50: 
51:             # Avoid loops where failing fact loads cause huge CPU
52:             # loops, this way it only retries once every cache_time
53:             # seconds
54:             @last_facts_load = Time.now.to_i
55: 
56:             # Revert to last known good state
57:             @facts = @last_good_facts.clone
58:           end
59:         end
60: 
61: 
62:         # If you do not supply a specific fact all facts will be returned
63:         if fact.nil?
64:           return @facts
65:         else
66:           @facts.include?(fact) ? @facts[fact] : nil
67:         end
68:       end

Returns all facts

[Source]

    # File lib/mcollective/facts/base.rb, line 71
71:       def get_facts
72:         get_fact(nil)
73:       end

Returns true if we know about a specific fact, false otherwise

[Source]

    # File lib/mcollective/facts/base.rb, line 76
76:       def has_fact?(fact)
77:         get_fact(nil).include?(fact)
78:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Logger.html0000644000175000017500000000642011747546503021560 0ustar jonasjonas Module: MCollective::Logger
Module MCollective::Logger
In: lib/mcollective/logger/syslog_logger.rb
lib/mcollective/logger/base.rb
lib/mcollective/logger/console_logger.rb
lib/mcollective/logger/file_logger.rb
lib/mcollective/logger.rb

Classes and Modules

Class MCollective::Logger::Base
Class MCollective::Logger::Console_logger
Class MCollective::Logger::File_logger
Class MCollective::Logger::Syslog_logger

[Validate]

mcollective-2.0.0/doc/classes/MCollective/PluginPackager.html0000644000175000017500000003603111747546503023236 0ustar jonasjonas Module: MCollective::PluginPackager
Module MCollective::PluginPackager
In: lib/mcollective/pluginpackager.rb
lib/mcollective/pluginpackager/standard_definition.rb
lib/mcollective/pluginpackager/agent_definition.rb

Methods

Classes and Modules

Class MCollective::PluginPackager::AgentDefinition
Class MCollective::PluginPackager::StandardDefinition

Public Class methods

[Source]

    # File lib/mcollective/pluginpackager.rb, line 12
12:     def self.[](klass)
13:       const_get("#{klass}")
14:     end

Checks if a build tool is present on the system

[Source]

    # File lib/mcollective/pluginpackager.rb, line 47
47:     def self.build_tool?(build_tool)
48:       ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
49:         builder = File.join(path, build_tool)
50:         if File.exists?(builder)
51:           return true
52:         end
53:       end
54:       false
55:     end

Checks if a directory is present and not empty

[Source]

    # File lib/mcollective/pluginpackager.rb, line 24
24:     def self.check_dir_present(path)
25:       (File.directory?(path) && !Dir.glob(File.join(path, "*")).empty?)
26:     end

Quietly calls a block if verbose parameter is false

[Source]

    # File lib/mcollective/pluginpackager.rb, line 29
29:     def self.do_quietly?(verbose, &block)
30:       unless verbose
31:         old_stdout = $stdout.clone
32:         $stdout.reopen(File.new("/dev/null", "w"))
33:         begin
34:           block.call
35:         rescue Exception => e
36:           $stdout.reopen old_stdout
37:           raise e
38:         ensure
39:           $stdout.reopen old_stdout
40:         end
41:       else
42:         block.call
43:       end
44:     end

Fetch and return metadata from plugin DDL

[Source]

    # File lib/mcollective/pluginpackager.rb, line 17
17:     def self.get_metadata(path, type)
18:       ddl = MCollective::RPC::DDL.new("package", false)
19:       ddl.instance_eval File.read(Dir.glob(File.join(path, type, "*.ddl")).first)
20:       ddl.meta
21:     end

Package implementation plugins

[Source]

    # File lib/mcollective/pluginpackager.rb, line 8
 8:     def self.load_packagers
 9:       PluginManager.find_and_load("pluginpackager")
10:     end

[Source]

    # File lib/mcollective/pluginpackager.rb, line 57
57:     def self.safe_system(*args)
58:       raise RuntimeError, "Failed: #{args.join(' ')}" unless system *args
59:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Client.html0000644000175000017500000013333011747546503021560 0ustar jonasjonas Class: MCollective::Client
Class MCollective::Client
In: lib/mcollective/client.rb
Parent: Object

Helpers for writing clients that can talk to agents, do discovery and so forth

Methods

Attributes

options  [RW] 
stats  [RW] 

Public Class methods

[Source]

    # File lib/mcollective/client.rb, line 6
 6:     def initialize(configfile)
 7:       @config = Config.instance
 8:       @config.loadconfig(configfile) unless @config.configured
 9: 
10:       @connection = PluginManager["connector_plugin"]
11:       @security = PluginManager["security_plugin"]
12: 
13:       @security.initiated_by = :client
14:       @options = nil
15:       @subscriptions = {}
16: 
17:       @connection.connect
18:     end

Public Instance methods

Returns the configured main collective if no specific collective is specified as options

[Source]

    # File lib/mcollective/client.rb, line 22
22:     def collective
23:       if @options[:collective].nil?
24:         @config.main_collective
25:       else
26:         @options[:collective]
27:       end
28:     end

Disconnects cleanly from the middleware

[Source]

    # File lib/mcollective/client.rb, line 31
31:     def disconnect
32:       Log.debug("Disconnecting from the middleware")
33:       @connection.disconnect
34:     end

Performs a discovery of nodes matching the filter passed returns an array of nodes

An integer limit can be supplied this will have the effect of the discovery being cancelled soon as it reached the requested limit of hosts

[Source]

     # File lib/mcollective/client.rb, line 114
114:     def discover(filter, timeout, limit=0)
115:       raise "Limit has to be an integer" unless limit.is_a?(Fixnum)
116: 
117:       begin
118:         hosts = []
119:         Timeout.timeout(timeout) do
120:           reqid = sendreq("ping", "discovery", filter)
121:           Log.debug("Waiting #{timeout} seconds for discovery replies to request #{reqid}")
122: 
123:           loop do
124:             reply = receive(reqid)
125:             Log.debug("Got discovery reply from #{reply.payload[:senderid]}")
126:             hosts << reply.payload[:senderid]
127: 
128:             return hosts if limit > 0 && hosts.size == limit
129:           end
130:         end
131:       rescue Timeout::Error => e
132:       rescue Exception => e
133:         raise
134:       ensure
135:         unsubscribe("discovery", :reply)
136:       end
137: 
138:       hosts.sort
139:     end

Performs a discovery and then send a request, performs the passed block for each response

   times = discovered_req("status", "mcollectived", options, client) {|resp|
      pp resp
   }

It returns a hash of times and timeouts for discovery and total run is taken from the options hash which in turn is generally built using MCollective::Optionparser

[Source]

     # File lib/mcollective/client.rb, line 201
201:     def discovered_req(body, agent, options=false)
202:       stat = {:starttime => Time.now.to_f, :discoverytime => 0, :blocktime => 0, :totaltime => 0}
203: 
204:       options = @options unless options
205: 
206:       STDOUT.sync = true
207: 
208:       print("Determining the amount of hosts matching filter for #{options[:disctimeout]} seconds .... ")
209: 
210:       begin
211:         discovered_hosts = discover(options[:filter], options[:disctimeout])
212:         discovered = discovered_hosts.size
213:         hosts_responded = []
214:         hosts_not_responded = discovered_hosts
215: 
216:         stat[:discoverytime] = Time.now.to_f - stat[:starttime]
217: 
218:         puts("#{discovered}\n\n")
219:       rescue Interrupt
220:         puts("Discovery interrupted.")
221:         exit!
222:       end
223: 
224:       raise("No matching clients found") if discovered == 0
225: 
226:       begin
227:         Timeout.timeout(options[:timeout]) do
228:           reqid = sendreq(body, agent, options[:filter])
229: 
230:           (1..discovered).each do |c|
231:             resp = receive(reqid)
232: 
233:             hosts_responded << resp.payload[:senderid]
234:             hosts_not_responded.delete(resp.payload[:senderid]) if hosts_not_responded.include?(resp.payload[:senderid])
235: 
236:             yield(resp.payload)
237:           end
238:         end
239:       rescue Interrupt => e
240:       rescue Timeout::Error => e
241:       end
242: 
243:       stat[:totaltime] = Time.now.to_f - stat[:starttime]
244:       stat[:blocktime] = stat[:totaltime] - stat[:discoverytime]
245:       stat[:responses] = hosts_responded.size
246:       stat[:responsesfrom] = hosts_responded
247:       stat[:noresponsefrom] = hosts_not_responded
248:       stat[:discovered] = discovered
249: 
250:       @stats = stat
251:       return stat
252:     end

Prints out the stats returns from req and discovered_req in a nice way

[Source]

     # File lib/mcollective/client.rb, line 255
255:     def display_stats(stats, options=false, caption="stomp call summary")
256:       options = @options unless options
257: 
258:       if options[:verbose]
259:         puts("\n---- #{caption} ----")
260: 
261:         if stats[:discovered]
262:           puts("           Nodes: #{stats[:discovered]} / #{stats[:responses]}")
263:         else
264:           puts("           Nodes: #{stats[:responses]}")
265:         end
266: 
267:         printf("      Start Time: %s\n", Time.at(stats[:starttime]))
268:         printf("  Discovery Time: %.2fms\n", stats[:discoverytime] * 1000)
269:         printf("      Agent Time: %.2fms\n", stats[:blocktime] * 1000)
270:         printf("      Total Time: %.2fms\n", stats[:totaltime] * 1000)
271: 
272:       else
273:         if stats[:discovered]
274:           printf("\nFinished processing %d / %d hosts in %.2f ms\n\n", stats[:responses], stats[:discovered], stats[:blocktime] * 1000)
275:         else
276:           printf("\nFinished processing %d hosts in %.2f ms\n\n", stats[:responses], stats[:blocktime] * 1000)
277:         end
278:       end
279: 
280:       if stats[:noresponsefrom].size > 0
281:         puts("\nNo response from:\n")
282: 
283:         stats[:noresponsefrom].each do |c|
284:           puts if c % 4 == 1
285:           printf("%30s", c)
286:         end
287: 
288:         puts
289:       end
290:     end

Blocking call that waits for ever for a message to arrive.

If you give it a requestid this means you‘ve previously send a request with that ID and now you just want replies that matches that id, in that case the current connection will just ignore all messages not directed at it and keep waiting for more till it finds a matching message.

[Source]

     # File lib/mcollective/client.rb, line 84
 84:     def receive(requestid = nil)
 85:       reply = nil
 86: 
 87:       begin
 88:         reply = @connection.receive
 89:         reply.type = :reply
 90:         reply.expected_msgid = requestid
 91: 
 92:         reply.decode!
 93: 
 94:         reply.payload[:senderid] = Digest::MD5.hexdigest(reply.payload[:senderid]) if ENV.include?("MCOLLECTIVE_ANON")
 95: 
 96:         raise(MsgDoesNotMatchRequestID, "Message reqid #{requestid} does not match our reqid #{reply.requestid}") unless reply.requestid == requestid
 97:       rescue SecurityValidationFailed => e
 98:         Log.warn("Ignoring a message that did not pass security validations")
 99:         retry
100:       rescue MsgDoesNotMatchRequestID => e
101:         Log.debug("Ignoring a message for some other client")
102:         retry
103:       end
104: 
105:       reply
106:     end

Send a request, performs the passed block for each response

times = req("status", "mcollectived", options, client) {|resp|

  pp resp

}

It returns a hash of times and timeouts for discovery and total run is taken from the options hash which in turn is generally built using MCollective::Optionparser

[Source]

     # File lib/mcollective/client.rb, line 149
149:     def req(body, agent=nil, options=false, waitfor=0)
150:       if body.is_a?(Message)
151:         agent = body.agent
152:         options = body.options
153:         waitfor = body.discovered_hosts.size || 0
154:       end
155: 
156:       stat = {:starttime => Time.now.to_f, :discoverytime => 0, :blocktime => 0, :totaltime => 0}
157: 
158:       options = @options unless options
159: 
160:       STDOUT.sync = true
161: 
162:       hosts_responded = 0
163: 
164:       begin
165:         Timeout.timeout(options[:timeout]) do
166:           reqid = sendreq(body, agent, options[:filter])
167: 
168:           loop do
169:             resp = receive(reqid)
170: 
171:             hosts_responded += 1
172: 
173:             yield(resp.payload)
174: 
175:             break if (waitfor != 0 && hosts_responded >= waitfor)
176:           end
177:         end
178:       rescue Interrupt => e
179:       rescue Timeout::Error => e
180:       ensure
181:         unsubscribe(agent, :reply)
182:       end
183: 
184:       stat[:totaltime] = Time.now.to_f - stat[:starttime]
185:       stat[:blocktime] = stat[:totaltime] - stat[:discoverytime]
186:       stat[:responses] = hosts_responded
187:       stat[:noresponsefrom] = []
188: 
189:       @stats = stat
190:       return stat
191:     end

Sends a request and returns the generated request id, doesn‘t wait for responses and doesn‘t execute any passed in code blocks for responses

[Source]

    # File lib/mcollective/client.rb, line 38
38:     def sendreq(msg, agent, filter = {})
39:       if msg.is_a?(Message)
40:         request = msg
41:         agent = request.agent
42:       else
43:         ttl = @options[:ttl] || @config.ttl
44:         request = Message.new(msg, nil, {:agent => agent, :type => :request, :collective => collective, :filter => filter, :ttl => ttl})
45:         request.reply_to = @options[:reply_to] if @options[:reply_to]
46:       end
47: 
48:       request.encode!
49: 
50:       Log.debug("Sending request #{request.requestid} to the #{request.agent} agent with ttl #{request.ttl} in collective #{request.collective}")
51: 
52:       subscribe(agent, :reply)
53: 
54:       request.publish
55: 
56:       request.requestid
57:     end

[Source]

    # File lib/mcollective/client.rb, line 59
59:     def subscribe(agent, type)
60:       unless @subscriptions.include?(agent)
61:         subscription = Util.make_subscriptions(agent, type, collective)
62:         Log.debug("Subscribing to #{type} target for agent #{agent}")
63: 
64:         Util.subscribe(subscription)
65:         @subscriptions[agent] = 1
66:       end
67:     end

[Source]

    # File lib/mcollective/client.rb, line 69
69:     def unsubscribe(agent, type)
70:       if @subscriptions.include?(agent)
71:         subscription = Util.make_subscriptions(agent, type, collective)
72:         Log.debug("Unsubscribing #{type} target for #{agent}")
73: 
74:         Util.unsubscribe(subscription)
75:         @subscriptions.delete(agent)
76:       end
77:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Registration.html0000644000175000017500000000632011747546503023012 0ustar jonasjonas Module: MCollective::Registration
Module MCollective::Registration
In: lib/mcollective/registration.rb
lib/mcollective/registration/base.rb

Registration is implimented using a module structure and installations can configure which module they want to use.

We provide a simple one that just sends back the list of current known agents in MCollective::Registration::Agentlist, you can create your own:

Create a module in plugins/mcollective/registration/<yourplugin>.rb

You can inherit from MCollective::Registration::Base in which case you just need to supply a body method, whatever this method returns will be send to the middleware connection for an agent called registration

Classes and Modules

Class MCollective::Registration::Base

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Log.html0000644000175000017500000004636311747546503021074 0ustar jonasjonas Class: MCollective::Log
Class MCollective::Log
In: lib/mcollective/log.rb
Parent: Object

A simple class that allows logging at various levels.

Methods

configure   cycle_level   debug   error   fatal   from   info   instance   log   logger   set_logger   warn  

Public Class methods

configures the logger class, if the config has not yet been loaded we default to the console logging class and do not set @configured so that future calls to the log method will keep attempting to configure the logger till we eventually get a logging preference from the config module

[Source]

    # File lib/mcollective/log.rb, line 72
72:       def configure(logger=nil)
73:         unless logger
74:           logger_type = "console"
75: 
76:           config = Config.instance
77: 
78:           if config.configured
79:             logger_type = config.logger_type
80:             @configured = true
81:           end
82: 
83:           require "mcollective/logger/#{logger_type.downcase}_logger"
84:           set_logger(eval("MCollective::Logger::#{logger_type.capitalize}_logger.new"))
85:         else
86:           set_logger(logger)
87:           @configured = true
88:         end
89: 
90: 
91:         @logger.start
92:       rescue Exception => e
93:         @configured = false
94:         STDERR.puts "Could not start logger: #{e.class} #{e}"
95:       end

increments the active log level

[Source]

    # File lib/mcollective/log.rb, line 43
43:       def cycle_level
44:         @logger.cycle_level if @configured
45:       end

Logs at debug level

[Source]

    # File lib/mcollective/log.rb, line 23
23:       def debug(msg)
24:         log(:debug, msg)
25:       end

Logs at error level

[Source]

    # File lib/mcollective/log.rb, line 33
33:       def error(msg)
34:         log(:error, msg)
35:       end

Logs at fatal level

[Source]

    # File lib/mcollective/log.rb, line 28
28:       def fatal(msg)
29:         log(:fatal, msg)
30:       end

figures out the filename that called us

[Source]

     # File lib/mcollective/log.rb, line 98
 98:       def from
 99:         from = File.basename(caller[2])
100:       end

Logs at info level

[Source]

    # File lib/mcollective/log.rb, line 13
13:       def info(msg)
14:         log(:info, msg)
15:       end

handle old code that relied on this class being a singleton

[Source]

    # File lib/mcollective/log.rb, line 38
38:       def instance
39:         self
40:       end

logs a message at a certain level

[Source]

    # File lib/mcollective/log.rb, line 48
48:       def log(level, msg)
49:         configure unless @configured
50: 
51:         raise "Unknown log level" unless [:error, :fatal, :debug, :warn, :info].include?(level)
52: 
53:         if @logger
54:           @logger.log(level, from, msg)
55:         else
56:           t = Time.new.strftime("%H:%M:%S")
57: 
58:           STDERR.puts "#{t}: #{level}: #{from}: #{msg}"
59:         end
60:       end

Obtain the class name of the currently configured logger

[Source]

    # File lib/mcollective/log.rb, line 8
 8:       def logger
 9:         @logger.class
10:       end

sets the logger class to use

[Source]

    # File lib/mcollective/log.rb, line 63
63:       def set_logger(logger)
64:         @logger = logger
65:       end

Logs at warn level

[Source]

    # File lib/mcollective/log.rb, line 18
18:       def warn(msg)
19:         log(:warn, msg)
20:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Registration/0000755000175000017500000000000011747546503022123 5ustar jonasjonasmcollective-2.0.0/doc/classes/MCollective/Registration/Base.html0000644000175000017500000003454111747546503023672 0ustar jonasjonas Class: MCollective::Registration::Base
Class MCollective::Registration::Base
In: lib/mcollective/registration/base.rb
Parent: Object

This is a base class that other registration plugins can use to handle regular announcements to the mcollective

The configuration file determines how often registration messages gets sent using the registerinterval option, the plugin runs in the background in a thread.

Methods

Public Class methods

Register plugins that inherits base

[Source]

    # File lib/mcollective/registration/base.rb, line 11
11:       def self.inherited(klass)
12:         PluginManager << {:type => "registration_plugin", :class => klass.to_s}
13:       end

Public Instance methods

[Source]

    # File lib/mcollective/registration/base.rb, line 36
36:       def config
37:         Config.instance
38:       end

[Source]

    # File lib/mcollective/registration/base.rb, line 57
57:       def interval
58:         config.registerinterval
59:       end

[Source]

    # File lib/mcollective/registration/base.rb, line 40
40:       def msg_filter
41:         {"agent" => "registration"}
42:       end

[Source]

    # File lib/mcollective/registration/base.rb, line 61
61:       def publish(message)
62:         unless message
63:           Log.debug("Skipping registration due to nil body")
64:         else
65:           req = Message.new(message, nil, {:type => :request, :agent => "registration", :collective => target_collective, :filter => msg_filter})
66:           req.encode!
67: 
68:           Log.debug("Sending registration #{req.requestid} to collective #{req.collective}")
69: 
70:           req.publish
71:         end
72:       end

Creates a background thread that periodically send a registration notice.

The actual registration notices comes from the ‘body’ method of the registration plugins.

[Source]

    # File lib/mcollective/registration/base.rb, line 19
19:       def run(connection)
20:         return false if interval == 0
21: 
22:         Thread.new do
23:           loop do
24:             begin
25:               publish(body)
26: 
27:               sleep interval
28:             rescue Exception => e
29:               Log.error("Sending registration message failed: #{e}")
30:               sleep interval
31:             end
32:           end
33:         end
34:       end

[Source]

    # File lib/mcollective/registration/base.rb, line 44
44:       def target_collective
45:         main_collective = config.main_collective
46: 
47:         collective = config.registration_collective || main_collective
48: 
49:         unless config.collectives.include?(collective)
50:           Log.warn("Sending registration to #{main_collective}: #{collective} is not a valid collective")
51:           collective = main_collective
52:         end
53: 
54:         return collective
55:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Application.html0000644000175000017500000016164111747546503022613 0ustar jonasjonas Class: MCollective::Application
Class MCollective::Application
In: lib/mcollective/application.rb
Parent: Object

Methods

Included Modules

RPC

Public Class methods

retrieves a specific option

[Source]

    # File lib/mcollective/application.rb, line 20
20:       def [](option)
21:         intialize_application_options unless @application_options
22:         @application_options[option]
23:       end

set an option in the options hash

[Source]

    # File lib/mcollective/application.rb, line 14
14:       def []=(option, value)
15:         intialize_application_options unless @application_options
16:         @application_options[option] = value
17:       end

Intialize a blank set of options if its the first time used else returns active options

[Source]

    # File lib/mcollective/application.rb, line 8
 8:       def application_options
 9:         intialize_application_options unless @application_options
10:         @application_options
11:       end

Sets the application description, there can be only one description per application so multiple calls will just change the description

[Source]

    # File lib/mcollective/application.rb, line 28
28:       def description(descr)
29:         self[:description] = descr
30:       end

[Source]

    # File lib/mcollective/application.rb, line 38
38:       def exclude_argument_sections(*sections)
39:         sections = [sections].flatten
40: 
41:         sections.each do |s|
42:           raise "Unknown CLI argument section #{s}" unless ["rpc", "common", "filter"].include?(s)
43:         end
44: 
45:         intialize_application_options unless @application_options
46:         self[:exclude_arg_sections] = sections
47:       end

Creates an empty set of options

[Source]

    # File lib/mcollective/application.rb, line 79
79:       def intialize_application_options
80:         @application_options = {:description          => nil,
81:                                 :usage                => [],
82:                                 :cli_arguments        => [],
83:                                 :exclude_arg_sections => []}
84:       end

Wrapper to create command line options

 - name: varaible name that will be used to access the option value
 - description: textual info shown in --help
 - arguments: a list of possible arguments that can be used
   to activate this option
 - type: a data type that ObjectParser understand of :bool or :array
 - required: true or false if this option has to be supplied
 - validate: a proc that will be called with the value used to validate
   the supplied value

  option :foo,
         :description => "The foo option"
         :arguments   => ["--foo ARG"]

after this the value supplied will be in configuration[:foo]

[Source]

    # File lib/mcollective/application.rb, line 65
65:       def option(name, arguments)
66:         opt = {:name => name,
67:                :description => nil,
68:                :arguments => [],
69:                :type => String,
70:                :required => false,
71:                :validate => Proc.new { true }}
72: 
73:         arguments.each_pair{|k,v| opt[k] = v}
74: 
75:         self[:cli_arguments] << opt
76:       end

Supplies usage information, calling multiple times will create multiple usage lines in —help output

[Source]

    # File lib/mcollective/application.rb, line 34
34:       def usage(usage)
35:         self[:usage] << usage
36:       end

Public Instance methods

Returns an array of all the arguments built using calls to optin

[Source]

     # File lib/mcollective/application.rb, line 245
245:     def application_cli_arguments
246:       application_options[:cli_arguments]
247:     end

Retrieve the current application description

[Source]

     # File lib/mcollective/application.rb, line 232
232:     def application_description
233:       application_options[:description]
234:     end

Handles failure, if we‘re far enough in the initialization phase it will log backtraces if its in verbose mode only

[Source]

     # File lib/mcollective/application.rb, line 251
251:     def application_failure(e, err_dest=STDERR)
252:       # peole can use exit() anywhere and not get nasty backtraces as a result
253:       if e.is_a?(SystemExit)
254:         disconnect
255:         raise(e)
256:       end
257: 
258:       err_dest.puts "#{$0} failed to run: #{e} (#{e.class})"
259: 
260:       if options.nil? || options[:verbose]
261:         e.backtrace.each{|l| err_dest.puts "\tfrom #{l}"}
262:       end
263: 
264:       disconnect
265: 
266:       exit 1
267:     end

Retrieves the full hash of application options

[Source]

     # File lib/mcollective/application.rb, line 227
227:     def application_options
228:       self.class.application_options
229:     end

Builds an ObjectParser config, parse the CLI options and validates based on the option config

[Source]

     # File lib/mcollective/application.rb, line 135
135:     def application_parse_options(help=false)
136:       @options ||= {:verbose => false}
137: 
138:       @options = clioptions(help) do |parser, options|
139:         parser.define_head application_description if application_description
140:         parser.banner = ""
141: 
142:         if application_usage
143:           parser.separator ""
144: 
145:           application_usage.each do |u|
146:             parser.separator "Usage: #{u}"
147:           end
148: 
149:           parser.separator ""
150:         end
151: 
152:         parser.define_tail ""
153:         parser.define_tail "The Marionette Collective #{MCollective.version}"
154: 
155: 
156:         application_cli_arguments.each do |carg|
157:           opts_array = []
158: 
159:           opts_array << :on
160: 
161:           # if a default is set from the application set it up front
162:           if carg.include?(:default)
163:             configuration[carg[:name]] = carg[:default]
164:           end
165: 
166:           # :arguments are multiple possible ones
167:           if carg[:arguments].is_a?(Array)
168:             carg[:arguments].each {|a| opts_array << a}
169:           else
170:             opts_array << carg[:arguments]
171:           end
172: 
173:           # type was given and its not one of our special types, just pass it onto optparse
174:           opts_array << carg[:type] if carg[:type] and ! [:bool, :array].include?(carg[:type])
175: 
176:           opts_array << carg[:description]
177: 
178:           # Handle our special types else just rely on the optparser to handle the types
179:           if carg[:type] == :bool
180:             parser.send(*opts_array) do |v|
181:               validate_option(carg[:validate], carg[:name], v)
182: 
183:               configuration[carg[:name]] = true
184:             end
185: 
186:           elsif carg[:type] == :array
187:             parser.send(*opts_array) do |v|
188:               validate_option(carg[:validate], carg[:name], v)
189: 
190:               configuration[carg[:name]] = [] unless configuration.include?(carg[:name])
191:               configuration[carg[:name]] << v
192:             end
193: 
194:           else
195:             parser.send(*opts_array) do |v|
196:               validate_option(carg[:validate], carg[:name], v)
197: 
198:               configuration[carg[:name]] = v
199:             end
200:           end
201:         end
202:       end
203:     end

Return the current usage text false if nothing is set

[Source]

     # File lib/mcollective/application.rb, line 237
237:     def application_usage
238:       usage = application_options[:usage]
239: 
240:       usage.empty? ? false : usage
241:     end

Creates a standard options hash, pass in a block to add extra headings etc see Optionparser

[Source]

     # File lib/mcollective/application.rb, line 111
111:     def clioptions(help)
112:       oparser = Optionparser.new({:verbose => false, :progress_bar => true}, "filter", application_options[:exclude_arg_sections])
113: 
114:       options = oparser.parse do |parser, options|
115:         if block_given?
116:           yield(parser, options)
117:         end
118: 
119:         RPC::Helpers.add_simplerpc_options(parser, options) unless application_options[:exclude_arg_sections].include?("rpc")
120:       end
121: 
122:       return oparser.parser.help if help
123: 
124:       validate_cli_options
125: 
126:       post_option_parser(configuration) if respond_to?(:post_option_parser)
127: 
128:       return options
129:     rescue Exception => e
130:       application_failure(e)
131:     end

The application configuration built from CLI arguments

[Source]

    # File lib/mcollective/application.rb, line 88
88:     def configuration
89:       @application_configuration ||= {}
90:       @application_configuration
91:     end

[Source]

     # File lib/mcollective/application.rb, line 291
291:     def disconnect
292:       MCollective::PluginManager["connector_plugin"].disconnect
293:     rescue
294:     end

A helper that creates a consistent exit code for applications by looking at an instance of MCollective::RPC::Stats

Exit with 0 if nodes were discovered and all passed Exit with 0 if no discovery were done and > 0 responses were received Exit with 1 if no nodes were discovered Exit with 2 if nodes were discovered but some RPC requests failed Exit with 3 if nodes were discovered, but not responses receivedif Exit with 4 if no discovery were done and no responses were received

[Source]

     # File lib/mcollective/application.rb, line 312
312:     def halt(stats)
313:       request_stats = {:discoverytime => 0,
314:                        :discovered => 0,
315:                        :failcount => 0}.merge(stats.to_hash)
316: 
317:       # was discovery done?
318:       if request_stats[:discoverytime] != 0
319:         # was any nodes discovered
320:         if request_stats[:discovered] == 0
321:           exit 1
322: 
323:         # nodes were discovered, did we get responses
324:         elsif request_stats[:responses] == 0
325:           exit 3
326: 
327:         else
328:           # we got responses and discovery was done, no failures
329:           if request_stats[:failcount] == 0
330:             exit 0
331:           else
332:             exit 2
333:           end
334:         end
335:       else
336:         # discovery wasnt done and we got no responses
337:         if request_stats[:responses] == 0
338:           exit 4
339:         else
340:           exit 0
341:         end
342:       end
343:     end

[Source]

     # File lib/mcollective/application.rb, line 269
269:     def help
270:       application_parse_options(true)
271:     end

Fake abstract class that logs if the user tries to use an application without supplying a main override method.

[Source]

     # File lib/mcollective/application.rb, line 298
298:     def main
299:       STDERR.puts "Applications need to supply a 'main' method"
300:       exit 1
301:     end

The active options hash used for MC::Client and other configuration

[Source]

    # File lib/mcollective/application.rb, line 94
94:     def options
95:       @options
96:     end

Wrapper around MC::RPC#rpcclient that forcably supplies our options hash if someone forgets to pass in options in an application the filters and other cli options wouldnt take effect which could have a disasterous outcome

[Source]

     # File lib/mcollective/application.rb, line 348
348:     def rpcclient(agent, flags = {})
349:       flags[:options] = options unless flags.include?(:options)
350: 
351:       super
352:     end

The main logic loop, builds up the options, validate configuration and calls the main as supplied by the user. Disconnects when done and pass any exception onto the application_failure helper

[Source]

     # File lib/mcollective/application.rb, line 276
276:     def run
277:       application_parse_options
278: 
279:       validate_configuration(configuration) if respond_to?(:validate_configuration)
280: 
281:       Util.setup_windows_sleeper if Util.windows?
282: 
283:       main
284: 
285:       disconnect
286: 
287:     rescue Exception => e
288:       application_failure(e)
289:     end

[Source]

     # File lib/mcollective/application.rb, line 205
205:     def validate_cli_options
206:       # Check all required parameters were set
207:       validation_passed = true
208:       application_cli_arguments.each do |carg|
209:         # Check for required arguments
210:         if carg[:required]
211:           unless configuration[ carg[:name] ]
212:             validation_passed = false
213:             STDERR.puts "The #{carg[:name]} option is mandatory"
214:           end
215:         end
216:       end
217: 
218:       unless validation_passed
219:         STDERR.puts "\nPlease run with --help for detailed help"
220:         exit 1
221:       end
222: 
223: 
224:     end

Calls the supplied block in an option for validation, an error raised will log to STDERR and exit the application

[Source]

     # File lib/mcollective/application.rb, line 100
100:     def validate_option(blk, name, value)
101:       validation_result = blk.call(value)
102: 
103:       unless validation_result == true
104:         STDERR.puts "Validation of #{name} failed: #{validation_result}"
105:         exit 1
106:       end
107:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/DDLValidationError.html0000644000175000017500000000445511747546503023777 0ustar jonasjonas Class: MCollective::DDLValidationError
Class MCollective::DDLValidationError
In: lib/mcollective.rb
Parent: RuntimeError

[Validate]

mcollective-2.0.0/doc/classes/MCollective/RPCAborted.html0000644000175000017500000000452611747546503022273 0ustar jonasjonas Class: MCollective::RPCAborted
Class MCollective::RPCAborted
In: lib/mcollective.rb
Parent: RPCError

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Connector/0000755000175000017500000000000011747546503021403 5ustar jonasjonasmcollective-2.0.0/doc/classes/MCollective/Connector/Base.html0000644000175000017500000000763311747546503023154 0ustar jonasjonas Class: MCollective::Connector::Base
Class MCollective::Connector::Base
In: lib/mcollective/connector/base.rb
Parent: Object

Methods

inherited  

Public Class methods

[Source]

    # File lib/mcollective/connector/base.rb, line 19
19:       def self.inherited(klass)
20:         PluginManager << {:type => "connector_plugin", :class => klass.to_s}
21:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Applications.html0000644000175000017500000005421511747546503022774 0ustar jonasjonas Class: MCollective::Applications
Class MCollective::Applications
In: lib/mcollective/applications.rb
Parent: Object

Methods

Public Class methods

[Source]

   # File lib/mcollective/applications.rb, line 3
3:     def self.[](appname)
4:       load_application(appname)
5:       PluginManager["#{appname}_application"]
6:     end

Filters a string of opts out using Shellwords keeping only things related to —config and -c

[Source]

    # File lib/mcollective/applications.rb, line 60
60:     def self.filter_extra_options(opts)
61:       res = ""
62:       words = Shellwords.shellwords(opts)
63:       words.each_with_index do |word,idx|
64:         if word == "-c"
65:           return "--config=#{words[idx + 1]}"
66:         elsif word == "--config"
67:           return "--config=#{words[idx + 1]}"
68:         elsif word =~ /\-c=/
69:           return word
70:         elsif word =~ /\-\-config=/
71:           return word
72:         end
73:       end
74: 
75:       return ""
76:     end

Returns an array of applications found in the lib dirs

[Source]

    # File lib/mcollective/applications.rb, line 36
36:     def self.list
37:       load_config
38: 
39:       applist = []
40: 
41:       Config.instance.libdir.each do |libdir|
42:         applicationdir = "#{libdir}/mcollective/application"
43:         next unless File.directory?(applicationdir)
44: 
45:         Dir.entries(applicationdir).grep(/\.rb$/).each do |application|
46:           applist << File.basename(application, ".rb")
47:         end
48:       end
49: 
50:       applist
51:     rescue SystemExit
52:       exit 1
53:     rescue Exception => e
54:       STDERR.puts "Failed to generate application list: #{e.class}: #{e}"
55:       exit 1
56:     end

[Source]

    # File lib/mcollective/applications.rb, line 26
26:     def self.load_application(appname)
27:       return if PluginManager.include?("#{appname}_application")
28: 
29:       load_config
30: 
31:       PluginManager.loadclass "MCollective::Application::#{appname.capitalize}"
32:       PluginManager << {:type => "#{appname}_application", :class => "MCollective::Application::#{appname.capitalize}"}
33:     end

We need to know the config file in order to know the libdir so that we can find applications.

The problem is the CLI might be stuffed with options only the app in the libdir might understand so we have a chicken and egg situation.

We‘re parsing and filtering MCOLLECTIVE_EXTRA_OPTS removing all but config related options and parsing the options looking just for the config file.

We‘re handling failures gracefully and finally restoring the ARG and MCOLLECTIVE_EXTRA_OPTS to the state they were before we started parsing.

This is mostly a hack, when we‘re redoing how config works this stuff should be made less sucky

[Source]

     # File lib/mcollective/applications.rb, line 95
 95:     def self.load_config
 96:       return if Config.instance.configured
 97: 
 98:       original_argv = ARGV.clone
 99:       original_extra_opts = ENV["MCOLLECTIVE_EXTRA_OPTS"].clone rescue nil
100:       configfile = nil
101: 
102:       parser = OptionParser.new
103:       parser.on("--config CONFIG", "-c", "Config file") do |f|
104:         configfile = f
105:       end
106: 
107:       parser.program_name = $0
108: 
109:       parser.on("--help")
110: 
111:       # avoid option parsers own internal version handling that sux
112:       parser.on("-v", "--verbose")
113: 
114:       if original_extra_opts
115:         begin
116:           # optparse will parse the whole ENV in one go and refuse
117:           # to play along with the retry trick I do below so in
118:           # order to handle unknown options properly I parse out
119:           # only -c and --config deleting everything else and
120:           # then restore the environment variable later when I
121:           # am done with it
122:           ENV["MCOLLECTIVE_EXTRA_OPTS"] = filter_extra_options(ENV["MCOLLECTIVE_EXTRA_OPTS"].clone)
123:           parser.environment("MCOLLECTIVE_EXTRA_OPTS")
124:         rescue Exception => e
125:           Log.error("Failed to parse MCOLLECTIVE_EXTRA_OPTS: #{e}")
126:         end
127: 
128:         ENV["MCOLLECTIVE_EXTRA_OPTS"] = original_extra_opts.clone
129:       end
130: 
131:       begin
132:         parser.parse!
133:       rescue OptionParser::InvalidOption => e
134:         retry
135:       end
136: 
137:       ARGV.clear
138:       original_argv.each {|a| ARGV << a}
139: 
140:       configfile = Util.config_file_for_user unless configfile
141: 
142:       Config.instance.loadconfig(configfile)
143:     end

[Source]

    # File lib/mcollective/applications.rb, line 8
 8:     def self.run(appname)
 9:       load_config
10: 
11:       begin
12:         load_application(appname)
13:       rescue Exception => e
14:         e.backtrace.first << RPC::Helpers.colorize(:red, "  <----")
15:         STDERR.puts "Application '#{appname}' failed to load:"
16:         STDERR.puts
17:         STDERR.puts RPC::Helpers.colorize(:red, "   #{e} (#{e.class})")
18:         STDERR.puts
19:         STDERR.puts "       %s" % [e.backtrace.join("\n       ")]
20:         exit 1
21:       end
22: 
23:       PluginManager["#{appname}_application"].run
24:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/UnknownRPCError.html0000644000175000017500000000454011747546503023360 0ustar jonasjonas Class: MCollective::UnknownRPCError
Class MCollective::UnknownRPCError
In: lib/mcollective.rb
Parent: RPCError

[Validate]

mcollective-2.0.0/doc/classes/MCollective/MsgDoesNotMatchRequestID.html0000644000175000017500000000447111747546503025132 0ustar jonasjonas Class: MCollective::MsgDoesNotMatchRequestID
Class MCollective::MsgDoesNotMatchRequestID
In: lib/mcollective.rb
Parent: RuntimeError

[Validate]

mcollective-2.0.0/doc/classes/MCollective/SSL.html0000644000175000017500000013007011747546503021001 0ustar jonasjonas Class: MCollective::SSL
Class MCollective::SSL
In: lib/mcollective/ssl.rb
Parent: Object

A class that assists in encrypting and decrypting data using a combination of RSA and AES

Data will be AES encrypted for speed, the Key used in # the AES stage will be encrypted using RSA

  ssl = SSL.new(public_key, private_key, passphrase)

  data = File.read("largefile.dat")

  crypted_data = ssl.encrypt_with_private(data)

  pp crypted_data

This will result in a hash of data like:

  crypted = {:key  => "crd4NHvG....=",
             :data => "XWXlqN+i...=="}

The key and data will all be base 64 encoded already by default you can pass a 2nd parameter as false to encrypt_with_private and counterparts that will prevent the base 64 encoding

You can pass the data hash into ssl.decrypt_with_public which should return your original data

There are matching methods for using a public key to encrypt data to be decrypted using a private key

Methods

Attributes

private_key_file  [R] 
public_key_file  [R] 
ssl_cipher  [R] 

Public Class methods

[Source]

     # File lib/mcollective/ssl.rb, line 195
195:     def self.base64_decode(string)
196:       Base64.decode64(string)
197:     end

[Source]

     # File lib/mcollective/ssl.rb, line 186
186:     def self.base64_encode(string)
187:       Base64.encode64(string)
188:     end

[Source]

     # File lib/mcollective/ssl.rb, line 203
203:     def self.md5(string)
204:       Digest::MD5.hexdigest(string)
205:     end

[Source]

    # File lib/mcollective/ssl.rb, line 37
37:     def initialize(pubkey=nil, privkey=nil, passphrase=nil, cipher=nil)
38:       @public_key_file = pubkey
39:       @private_key_file = privkey
40: 
41:       @public_key  = read_key(:public, pubkey)
42:       @private_key = read_key(:private, privkey, passphrase)
43: 
44:       @ssl_cipher = "aes-256-cbc"
45:       @ssl_cipher = Config.instance.ssl_cipher if Config.instance.ssl_cipher
46:       @ssl_cipher = cipher if cipher
47: 
48:       raise "The supplied cipher '#{@ssl_cipher}' is not supported" unless OpenSSL::Cipher.ciphers.include?(@ssl_cipher)
49:     end

Public Instance methods

decrypts a string given key, iv and data

[Source]

     # File lib/mcollective/ssl.rb, line 158
158:     def aes_decrypt(key, crypt_string)
159:       cipher = OpenSSL::Cipher::Cipher.new(ssl_cipher)
160: 
161:       cipher.decrypt
162:       cipher.key = key
163:       cipher.pkcs5_keyivgen(key)
164:       decrypted_data = cipher.update(crypt_string) + cipher.final
165:     end

encrypts a string, returns a hash of key, iv and data

[Source]

     # File lib/mcollective/ssl.rb, line 144
144:     def aes_encrypt(plain_string)
145:       cipher = OpenSSL::Cipher::Cipher.new(ssl_cipher)
146:       cipher.encrypt
147: 
148:       key = cipher.random_key
149: 
150:       cipher.key = key
151:       cipher.pkcs5_keyivgen(key)
152:       encrypted_data = cipher.update(plain_string) + cipher.final
153: 
154:       {:key => key, :data => encrypted_data}
155:     end

base 64 decode a string

[Source]

     # File lib/mcollective/ssl.rb, line 191
191:     def base64_decode(string)
192:       SSL.base64_decode(string)
193:     end

base 64 encode a string

[Source]

     # File lib/mcollective/ssl.rb, line 182
182:     def base64_encode(string)
183:       SSL.base64_encode(string)
184:     end

Decrypts data, expects a hash as create with crypt_with_public

[Source]

    # File lib/mcollective/ssl.rb, line 88
88:     def decrypt_with_private(crypted, base64=true)
89:       raise "Crypted data should include a key" unless crypted.include?(:key)
90:       raise "Crypted data should include data" unless crypted.include?(:data)
91: 
92:       if base64
93:         key = rsa_decrypt_with_private(base64_decode(crypted[:key]))
94:         aes_decrypt(key, base64_decode(crypted[:data]))
95:       else
96:         key = rsa_decrypt_with_private(crypted[:key])
97:         aes_decrypt(key, crypted[:data])
98:       end
99:     end

Decrypts data, expects a hash as create with crypt_with_private

[Source]

     # File lib/mcollective/ssl.rb, line 102
102:     def decrypt_with_public(crypted, base64=true)
103:       raise "Crypted data should include a key" unless crypted.include?(:key)
104:       raise "Crypted data should include data" unless crypted.include?(:data)
105: 
106:       if base64
107:         key = rsa_decrypt_with_public(base64_decode(crypted[:key]))
108:         aes_decrypt(key, base64_decode(crypted[:data]))
109:       else
110:         key = rsa_decrypt_with_public(crypted[:key])
111:         aes_decrypt(key, crypted[:data])
112:       end
113:     end

Encrypts supplied data using AES and then encrypts using RSA the key and IV

Return a hash with everything optionally base 64 encoded

[Source]

    # File lib/mcollective/ssl.rb, line 73
73:     def encrypt_with_private(plain_text, base64=true)
74:       crypted = aes_encrypt(plain_text)
75: 
76:       if base64
77:         key = base64_encode(rsa_encrypt_with_private(crypted[:key]))
78:         data = base64_encode(crypted[:data])
79:       else
80:         key = rsa_encrypt_with_private(crypted[:key])
81:         data = crypted[:data]
82:       end
83: 
84:       {:key => key, :data => data}
85:     end

Encrypts supplied data using AES and then encrypts using RSA the key and IV

Return a hash with everything optionally base 64 encoded

[Source]

    # File lib/mcollective/ssl.rb, line 55
55:     def encrypt_with_public(plain_text, base64=true)
56:       crypted = aes_encrypt(plain_text)
57: 
58:       if base64
59:         key = base64_encode(rsa_encrypt_with_public(crypted[:key]))
60:         data = base64_encode(crypted[:data])
61:       else
62:         key = rsa_encrypt_with_public(crypted[:key])
63:         data = crypted[:data]
64:       end
65: 
66:       {:key => key, :data => data}
67:     end

[Source]

     # File lib/mcollective/ssl.rb, line 199
199:     def md5(string)
200:       SSL.md5(string)
201:     end

Reads either a :public or :private key from disk, uses an optional passphrase to read the private key

[Source]

     # File lib/mcollective/ssl.rb, line 209
209:     def read_key(type, key=nil, passphrase=nil)
210:       return key if key.nil?
211: 
212:       raise "Could not find key #{key}" unless File.exist?(key)
213: 
214:       if type == :public
215:         begin
216:           key = OpenSSL::PKey::RSA.new(File.read(key))
217:         rescue OpenSSL::PKey::RSAError
218:           key = OpenSSL::X509::Certificate.new(File.read(key)).public_key
219:         end
220: 
221:         # Ruby < 1.9.3 had a bug where it does not correctly clear the
222:         # queue of errors while reading a key.  It tries various ways
223:         # to read the key and each failing attempt pushes an error onto
224:         # the queue.  With pubkeys only the 3rd attempt pass leaving 2
225:         # stale errors on the error queue.
226:         #
227:         # In 1.9.3 they fixed this by simply discarding the errors after
228:         # every attempt.  So we simulate this fix here for older rubies
229:         # as without it we get SSL_read errors from the Stomp+TLS sessions
230:         #
231:         # We do this only on 1.8 relying on 1.9.3 to do the right thing
232:         # and we do not support 1.9 less than 1.9.3
233:         #
234:         # See  http://bugs.ruby-lang.org/issues/4550
235:         OpenSSL.errors if Util.ruby_version =~ /^1.8/
236: 
237:         return key
238:       elsif type == :private
239:         return OpenSSL::PKey::RSA.new(File.read(key), passphrase)
240:       else
241:         raise "Can only load :public or :private keys"
242:       end
243:     end

Use the private key to RSA decrypt data

[Source]

     # File lib/mcollective/ssl.rb, line 123
123:     def rsa_decrypt_with_private(crypt_string)
124:       raise "No private key set" unless @private_key
125: 
126:       @private_key.private_decrypt(crypt_string)
127:     end

Use the public key to RSA decrypt data

[Source]

     # File lib/mcollective/ssl.rb, line 137
137:     def rsa_decrypt_with_public(crypt_string)
138:       raise "No public key set" unless @public_key
139: 
140:       @public_key.public_decrypt(crypt_string)
141:     end

Use the private key to RSA encrypt data

[Source]

     # File lib/mcollective/ssl.rb, line 130
130:     def rsa_encrypt_with_private(plain_string)
131:       raise "No private key set" unless @private_key
132: 
133:       @private_key.private_encrypt(plain_string)
134:     end

Use the public key to RSA encrypt data

[Source]

     # File lib/mcollective/ssl.rb, line 116
116:     def rsa_encrypt_with_public(plain_string)
117:       raise "No public key set" unless @public_key
118: 
119:       @public_key.public_encrypt(plain_string)
120:     end

Signs a string using the private key

[Source]

     # File lib/mcollective/ssl.rb, line 168
168:     def sign(string, base64=false)
169:       sig = @private_key.sign(OpenSSL::Digest::SHA1.new, string)
170: 
171:       base64 ? base64_encode(sig) : sig
172:     end

Using the public key verifies that a string was signed using the private key

[Source]

     # File lib/mcollective/ssl.rb, line 175
175:     def verify_signature(signature, string, base64=false)
176:       signature = base64_decode(signature) if base64
177: 
178:       @public_key.verify(OpenSSL::Digest::SHA1.new, signature, string)
179:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Runner.html0000644000175000017500000002737011747546503021621 0ustar jonasjonas Class: MCollective::Runner
Class MCollective::Runner
In: lib/mcollective/runner.rb
Parent: Object

The main runner for the daemon, supports running in the foreground and the background, keeps detailed stats and provides hooks to access all this information

Methods

new   run  

Public Class methods

[Source]

    # File lib/mcollective/runner.rb, line 6
 6:     def initialize(configfile)
 7:       @config = Config.instance
 8:       @config.loadconfig(configfile) unless @config.configured
 9: 
10:       @stats = PluginManager["global_stats"]
11: 
12:       @security = PluginManager["security_plugin"]
13:       @security.initiated_by = :node
14: 
15:       @connection = PluginManager["connector_plugin"]
16:       @connection.connect
17: 
18:       @agents = Agents.new
19: 
20:       unless Util.windows?
21:         Signal.trap("USR1") do
22:           Log.info("Reloading all agents after receiving USR1 signal")
23:           @agents.loadagents
24:         end
25: 
26:         Signal.trap("USR2") do
27:           Log.info("Cycling logging level due to USR2 signal")
28:           Log.cycle_level
29:         end
30:       else
31:         Util.setup_windows_sleeper
32:       end
33:     end

Public Instance methods

Starts the main loop, before calling this you should initialize the MCollective::Config singleton.

[Source]

    # File lib/mcollective/runner.rb, line 36
36:     def run
37:       Util.subscribe(Util.make_subscriptions("mcollective", :broadcast))
38:       Util.subscribe(Util.make_subscriptions("mcollective", :directed)) if @config.direct_addressing
39: 
40:       # Start the registration plugin if interval isn't 0
41:       begin
42:         PluginManager["registration_plugin"].run(@connection) unless @config.registerinterval == 0
43:       rescue Exception => e
44:         Log.error("Failed to start registration plugin: #{e}")
45:       end
46: 
47:       loop do
48:         begin
49:           request = receive
50: 
51:           if request.agent == "mcollective"
52:             controlmsg(request)
53:           else
54:             agentmsg(request)
55:           end
56:         rescue SignalException => e
57:           Log.warn("Exiting after signal: #{e}")
58:           @connection.disconnect
59:           raise
60: 
61:         rescue MsgTTLExpired => e
62:           Log.warn(e)
63: 
64:         rescue NotTargettedAtUs => e
65:           Log.debug("Message does not pass filters, ignoring")
66: 
67:         rescue Exception => e
68:           Log.warn("Failed to handle message: #{e} - #{e.class}\n")
69:           Log.warn(e.backtrace.join("\n\t"))
70:         end
71:       end
72:     end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/PluginPackager/0000755000175000017500000000000011747546503022345 5ustar jonasjonasmcollective-2.0.0/doc/classes/MCollective/PluginPackager/StandardDefinition.html0000644000175000017500000004133411747546503027011 0ustar jonasjonas Class: MCollective::PluginPackager::StandardDefinition
Class MCollective::PluginPackager::StandardDefinition
In: lib/mcollective/pluginpackager/standard_definition.rb
Parent: Object

Methods

Attributes

dependencies  [RW] 
iteration  [RW] 
mccommon  [RW] 
mcserver  [RW] 
metadata  [RW] 
packagedata  [RW] 
path  [RW] 
plugintype  [RW] 
postinstall  [RW] 
preinstall  [RW] 
target_path  [RW] 
vendor  [RW] 

Public Class methods

[Source]

    # File lib/mcollective/pluginpackager/standard_definition.rb, line 8
 8:       def initialize(path, name, vendor, preinstall, postinstall, iteration, dependencies, mcodependency, plugintype)
 9:         @plugintype = plugintype
10:         @path = path
11:         @packagedata = {}
12:         @iteration = iteration || 1
13:         @preinstall = preinstall
14:         @postinstall = postinstall
15:         @vendor = vendor || "Puppet Labs"
16:         @dependencies = dependencies || []
17:         @mcserver = mcodependency[:server] || "mcollective"
18:         @mccommon = mcodependency[:common] || "mcollective-common"
19:         @target_path = File.expand_path(@path)
20:         @metadata = PluginPackager.get_metadata(@path, @plugintype)
21:         @metadata[:name] = (name || @metadata[:name]).downcase.gsub(" ", "-")
22:         identify_packages
23:       end

Public Instance methods

Obtain list of common files

[Source]

    # File lib/mcollective/pluginpackager/standard_definition.rb, line 51
51:       def common
52:         common = {:files => [],
53:                   :dependencies => @dependencies.clone << @mccommon,
54:                   :description => "Common libraries for #{@name} connector plugin"}
55: 
56:         commondir = File.join(@path, "util")
57:         if PluginPackager.check_dir_present commondir
58:           common[:files] = Dir.glob(File.join(commondir, "*"))
59:           return common
60:         else
61:           return nil
62:         end
63:       end

Identify present packages and populate the packagedata hash

[Source]

    # File lib/mcollective/pluginpackager/standard_definition.rb, line 26
26:       def identify_packages
27:         common_package = common
28:         @packagedata[:common] = common_package if common_package
29:         plugin_package = plugin
30:         @packagedata[@plugintype] = plugin_package if plugin_package
31:       end

Obtain standard plugin files and dependencies

[Source]

    # File lib/mcollective/pluginpackager/standard_definition.rb, line 34
34:       def plugin
35:         plugindata = {:files => [],
36:                       :dependencies => @dependencies.clone << @mcserver,
37:                       :description => "#{@name} #{@plugintype} plugin for the Marionette Collective."}
38: 
39:         plugindir = File.join(@path, @plugintype.to_s)
40:         if PluginPackager.check_dir_present plugindir
41:           plugindata[:files] = Dir.glob(File.join(plugindir, "*"))
42:         else
43:           return nil
44:         end
45: 
46:         plugindata[:dependencies] <<"mcollective-#{@metadata[:name]}-common" if @packagedata[:common]
47:         plugindata
48:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/PluginPackager/AgentDefinition.html0000644000175000017500000005644411747546503026317 0ustar jonasjonas Class: MCollective::PluginPackager::AgentDefinition
Class MCollective::PluginPackager::AgentDefinition
In: lib/mcollective/pluginpackager/agent_definition.rb
Parent: Object

MCollective Agent Plugin package

Methods

agent   client   common   identify_packages   new  

Attributes

dependencies  [RW] 
iteration  [RW] 
mcclient  [RW] 
mccommon  [RW] 
mcserver  [RW] 
metadata  [RW] 
packagedata  [RW] 
path  [RW] 
plugintype  [RW] 
postinstall  [RW] 
preinstall  [RW] 
target_path  [RW] 
vendor  [RW] 

Public Class methods

[Source]

    # File lib/mcollective/pluginpackager/agent_definition.rb, line 8
 8:       def initialize(path, name, vendor, preinstall, postinstall, iteration, dependencies, mcodependency, plugintype)
 9:         @plugintype = plugintype
10:         @path = path
11:         @packagedata = {}
12:         @iteration = iteration || 1
13:         @preinstall = preinstall
14:         @postinstall = postinstall
15:         @vendor = vendor || "Puppet Labs"
16:         @mcserver = mcodependency[:server] || "mcollective"
17:         @mcclient = mcodependency[:client] || "mcollective-client"
18:         @mccommon = mcodependency[:common] || "mcollective-common"
19:         @dependencies = dependencies || []
20:         @target_path = File.expand_path(@path)
21:         @metadata = PluginPackager.get_metadata(@path, "agent")
22:         @metadata[:name] = (name || @metadata[:name]).downcase.gsub(" ", "-")
23:         identify_packages
24:       end

Public Instance methods

Obtain Agent package files and dependencies.

[Source]

    # File lib/mcollective/pluginpackager/agent_definition.rb, line 37
37:       def agent
38:         agent = {:files => [],
39:                  :dependencies => @dependencies.clone << @mcserver,
40:                  :description => "Agent plugin for #{@metadata[:name]}"}
41: 
42:         agentdir = File.join(@path, "agent")
43: 
44:         if PluginPackager.check_dir_present agentdir
45:           ddls = Dir.glob(File.join(agentdir, "*.ddl"))
46:           agent[:files] = (Dir.glob(File.join(agentdir, "*")) - ddls)
47:           implementations = Dir.glob(File.join(@metadata[:name], "**"))
48:           agent[:files] += implementations unless implementations.empty?
49:         else
50:           return nil
51:         end
52:         agent[:dependencies] << "mcollective-#{@metadata[:name]}-common" if @packagedata[:common]
53:         agent
54:       end

Obtain client package files and dependencies.

[Source]

    # File lib/mcollective/pluginpackager/agent_definition.rb, line 57
57:       def client
58:         client = {:files => [],
59:                   :dependencies => @dependencies.clone << @mcclient,
60:                   :description => "Client plugin for #{@metadata[:name]}"}
61: 
62:         clientdir = File.join(@path, "application")
63:         bindir = File.join(@path, "bin")
64:         ddldir = File.join(@path, "agent")
65: 
66:         client[:files] += Dir.glob(File.join(clientdir, "*")) if PluginPackager.check_dir_present clientdir
67:         client[:files] += Dir.glob(File.join(bindir,"*")) if PluginPackager.check_dir_present bindir
68:         client[:files] += Dir.glob(File.join(ddldir, "*.ddl")) if PluginPackager.check_dir_present ddldir
69:         client[:dependencies] << "mcollective-#{@metadata[:name]}-common" if @packagedata[:common]
70:         client[:files].empty? ? nil : client
71:       end

Obtain common package files and dependencies.

[Source]

    # File lib/mcollective/pluginpackager/agent_definition.rb, line 74
74:       def common
75:         common = {:files =>[],
76:                   :dependencies => @dependencies.clone << @mccommon,
77:                   :description => "Common libraries for #{@metadata[:name]}"}
78: 
79:         commondir = File.join(@path, "util")
80:         common[:files] += Dir.glob(File.join(commondir,"*")) if PluginPackager.check_dir_present commondir
81:         common[:files].empty? ? nil : common
82:       end

Identify present packages and populate packagedata hash.

[Source]

    # File lib/mcollective/pluginpackager/agent_definition.rb, line 27
27:       def identify_packages
28:         common_package = common
29:         @packagedata[:common] = common_package if common_package
30:         agent_package = agent
31:         @packagedata[:agent] = agent_package if agent_package
32:         client_package = client
33:         @packagedata[:client] = client_package if client_package
34:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/MissingRPCData.html0000644000175000017500000000453611747546503023117 0ustar jonasjonas Class: MCollective::MissingRPCData
Class MCollective::MissingRPCData
In: lib/mcollective.rb
Parent: RPCError

[Validate]

mcollective-2.0.0/doc/classes/MCollective/UnknownRPCAction.html0000644000175000017500000000454211747546503023506 0ustar jonasjonas Class: MCollective::UnknownRPCAction
Class MCollective::UnknownRPCAction
In: lib/mcollective.rb
Parent: RPCError

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Logger/0000755000175000017500000000000011747546503020670 5ustar jonasjonasmcollective-2.0.0/doc/classes/MCollective/Logger/File_logger.html0000644000175000017500000002454711747546503024010 0ustar jonasjonas Class: MCollective::Logger::File_logger
Class MCollective::Logger::File_logger
In: lib/mcollective/logger/file_logger.rb
Parent: Base

Impliments a file based logger using the standard ruby logger class

To configure you should set:

  - config.logfile
  - config.keeplogs defaults to 2097152
  - config.max_log_size defaults to 5

Methods

Public Instance methods

[Source]

    # File lib/mcollective/logger/file_logger.rb, line 37
37:       def log(level, from, msg)
38:         @logger.add(map_level(level)) { "#{from} #{msg}" }
39:       rescue
40:         # if this fails we probably cant show the user output at all,
41:         # STDERR it as last resort
42:         STDERR.puts("#{level}: #{msg}")
43:       end

[Source]

    # File lib/mcollective/logger/file_logger.rb, line 22
22:       def set_logging_level(level)
23:         @logger.level = map_level(level)
24:       rescue Exception => e
25:         @logger.level = ::Logger::DEBUG
26:         log(:error, "", "Could not set logging to #{level} using debug instead: #{e.class} #{e}")
27:       end

[Source]

    # File lib/mcollective/logger/file_logger.rb, line 13
13:       def start
14:         config = Config.instance
15: 
16:         @logger = ::Logger.new(config.logfile, config.keeplogs, config.max_log_size)
17:         @logger.formatter = ::Logger::Formatter.new
18: 
19:         set_level(config.loglevel.to_sym)
20:       end

[Source]

    # File lib/mcollective/logger/file_logger.rb, line 29
29:       def valid_levels
30:         {:info  => ::Logger::INFO,
31:           :warn  => ::Logger::WARN,
32:           :debug => ::Logger::DEBUG,
33:           :fatal => ::Logger::FATAL,
34:           :error => ::Logger::ERROR}
35:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Logger/Base.html0000644000175000017500000001752111747546503022436 0ustar jonasjonas Class: MCollective::Logger::Base
Class MCollective::Logger::Base
In: lib/mcollective/logger/base.rb
Parent: Object

A base class for logging providers.

Logging providers should provide the following:

   * start - all you need to do to setup your logging
   * set_logging_level - set your logging to :info, :warn, etc
   * valid_levels - a hash of maps from :info to your internal level name
   * log - what needs to be done to log a specific message

Methods

cycle_level   new   set_level  

Attributes

active_level  [R] 

Public Class methods

[Source]

    # File lib/mcollective/logger/base.rb, line 14
14:       def initialize
15:         @known_levels = [:debug, :info, :warn, :error, :fatal]
16: 
17:         # Sanity check the class that impliments the logging
18:         @known_levels.each do |lvl|
19:           raise "Logger class did not specify a map for #{lvl}" unless valid_levels.include?(lvl)
20:         end
21:       end

Public Instance methods

Figures out the next level and sets it

[Source]

    # File lib/mcollective/logger/base.rb, line 24
24:       def cycle_level
25:         lvl = get_next_level
26:         set_level(lvl)
27: 
28:         log(lvl, "", "Logging level is now #{lvl.to_s.upcase}")
29:       end

Sets a new level and record it in @active_level

[Source]

    # File lib/mcollective/logger/base.rb, line 32
32:       def set_level(level)
33:         set_logging_level(level)
34:         @active_level = level.to_sym
35:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Logger/Console_logger.html0000644000175000017500000003174411747546503024530 0ustar jonasjonas Class: MCollective::Logger::Console_logger
Class MCollective::Logger::Console_logger
In: lib/mcollective/logger/console_logger.rb
Parent: Base

Impliments a syslog based logger using the standard ruby syslog class

Methods

Public Instance methods

Set some colors for various logging levels, will honor the color configuration option and return nothing if its configured not to

[Source]

    # File lib/mcollective/logger/console_logger.rb, line 39
39:       def color(level)
40:         colorize = Config.instance.color
41: 
42:         colors = {:error => "",
43:           :fatal => "",
44:           :warn => "",
45:           :info => "",
46:           :reset => ""}
47: 
48:         if colorize
49:           return colors[level] || ""
50:         else
51:           return ""
52:         end
53:       end

Helper to return a string in specific color

[Source]

    # File lib/mcollective/logger/console_logger.rb, line 56
56:       def colorize(level, msg)
57:         "#{self.color(level)}#{msg}#{self.color(:reset)}"
58:       end

[Source]

    # File lib/mcollective/logger/console_logger.rb, line 24
24:       def log(level, from, msg)
25:         if @known_levels.index(level) >= @known_levels.index(@active_level)
26:           time = Time.new.strftime("%Y/%m/%d %H:%M:%S")
27:           lvltxt = colorize(level, level)
28:           STDERR.puts("#{lvltxt} #{time}: #{from} #{msg}")
29:         end
30:       rescue
31:         # if this fails we probably cant show the user output at all,
32:         # STDERR it as last resort
33:         STDERR.puts("#{level}: #{msg}")
34:       end

[Source]

    # File lib/mcollective/logger/console_logger.rb, line 12
12:       def set_logging_level(level)
13:         # nothing to do here, we ignore high levels when we log
14:       end

[Source]

    # File lib/mcollective/logger/console_logger.rb, line 5
 5:       def start
 6:         set_level(:info)
 7: 
 8:         config = Config.instance
 9:         set_level(config.loglevel.to_sym) if config.configured
10:       end

[Source]

    # File lib/mcollective/logger/console_logger.rb, line 16
16:       def valid_levels
17:         {:info  => :info,
18:           :warn  => :warning,
19:           :debug => :debug,
20:           :fatal => :crit,
21:           :error => :err}
22:       end

[Validate]

mcollective-2.0.0/doc/classes/MCollective/Logger/Syslog_logger.html0000644000175000017500000002643111747546503024403 0ustar jonasjonas Class: MCollective::Logger::Syslog_logger
Class MCollective::Logger::Syslog_logger
In: lib/mcollective/logger/syslog_logger.rb
Parent: Base

Implements a syslog based logger using the standard ruby syslog class

Methods

Included Modules

Syslog::Constants

Public Instance methods

[Source]

    # File lib/mcollective/logger/syslog_logger.rb, line 42
42:       def log(level, from, msg)
43:         if @known_levels.index(level) >= @known_levels.index(@active_level)
44:           Syslog.send(map_level(level), "#{from} #{msg}")
45:         end
46:       rescue
47:         # if this fails we probably cant show the user output at all,
48:         # STDERR it as last resort
49:         STDERR.puts("#{level}: #{msg}")
50:       end

[Source]

    # File lib/mcollective/logger/syslog_logger.rb, line 30
30:       def set_logging_level(level)
31:         # noop
32:       end

[Source]

    # File lib/mcollective/logger/syslog_logger.rb, line 9
 9:       def start
10:         config = Config.instance
11: 
12:         facility = syslog_facility(config.logfacility)
13:         level = config.loglevel.to_sym
14: 
15:         Syslog.close if Syslog.opened?
16:         Syslog.open(File.basename($0), 3, facility)
17: 
18:         set_level(level)
19:       end

[Source]

    # File lib/mcollective/logger/syslog_logger.rb, line 21
21:       def syslog_facility(facility)
22:         begin
23:           Syslog.const_get("LOG_#{facility.upcase}")
24:         rescue NameError => e
25:           STDERR.puts "Invalid syslog facility #{facility} supplied, reverting to USER"
26:           Syslog::LOG_USER
27:         end
28:       end

[Source]

    # File lib/mcollective/logger/syslog_logger.rb, line 34
34:       def valid_levels
35:         {:info  => :info,
36:          :warn  => :warning,
37:          :debug => :debug,
38:          :fatal => :crit,
39:          :error => :err}
40:       end

[Validate]

mcollective-2.0.0/doc/classes/Dir.html0000644000175000017500000002333111747546503016651 0ustar jonasjonas Class: Dir
Class Dir
In: lib/mcollective/monkey_patches.rb
Parent: Object

Methods

mktmpdir   tmpdir  

Public Class methods

[Source]

    # File lib/mcollective/monkey_patches.rb, line 55
55:   def self.mktmpdir(prefix_suffix=nil, tmpdir=nil)
56:     case prefix_suffix
57:     when nil
58:       prefix = "d"
59:       suffix = ""
60:     when String
61:       prefix = prefix_suffix
62:       suffix = ""
63:     when Array
64:       prefix = prefix_suffix[0]
65:       suffix = prefix_suffix[1]
66:     else
67:       raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
68:     end
69:     tmpdir ||= Dir.tmpdir
70:     t = Time.now.strftime("%Y%m%d")
71:     n = nil
72:     begin
73:       path = "#{tmpdir}/#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
74:       path << "-#{n}" if n
75:       path << suffix
76:       Dir.mkdir(path, 0700)
77:     rescue Errno::EEXIST
78:       n ||= 0
79:       n += 1
80:       retry
81:     end
82: 
83:     if block_given?
84:       begin
85:         yield path
86:       ensure
87:         FileUtils.remove_entry_secure path
88:       end
89:     else
90:       path
91:     end
92:   end

[Source]

     # File lib/mcollective/monkey_patches.rb, line 94
 94:   def self.tmpdir
 95:     tmp = '.'
 96:     for dir in [ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], '/tmp']
 97:       if dir and stat = File.stat(dir) and stat.directory? and stat.writable?
 98:         tmp = dir
 99:         break
100:       end rescue nil
101:     end
102:     File.expand_path(tmp)
103:   end

[Validate]

mcollective-2.0.0/doc/classes/MCollective.html0000644000175000017500000003504711747546503020350 0ustar jonasjonas Module: MCollective
Module MCollective
In: lib/mcollective/logger/syslog_logger.rb
lib/mcollective/logger/base.rb
lib/mcollective/logger/console_logger.rb
lib/mcollective/logger/file_logger.rb
lib/mcollective/optionparser.rb
lib/mcollective/registration.rb
lib/mcollective/log.rb
lib/mcollective/agents.rb
lib/mcollective/connector/base.rb
lib/mcollective/runner.rb
lib/mcollective/registration/base.rb
lib/mcollective/ssl.rb
lib/mcollective/application.rb
lib/mcollective/matcher/scanner.rb
lib/mcollective/matcher/parser.rb
lib/mcollective/rpc/reply.rb
lib/mcollective/rpc/request.rb
lib/mcollective/rpc/audit.rb
lib/mcollective/rpc/result.rb
lib/mcollective/rpc/progress.rb
lib/mcollective/rpc/actionrunner.rb
lib/mcollective/rpc/agent.rb
lib/mcollective/rpc/ddl.rb
lib/mcollective/rpc/client.rb
lib/mcollective/rpc/helpers.rb
lib/mcollective/rpc/stats.rb
lib/mcollective/shell.rb
lib/mcollective/applications.rb
lib/mcollective/rpc.rb
lib/mcollective/client.rb
lib/mcollective/pluginmanager.rb
lib/mcollective/facts.rb
lib/mcollective/message.rb
lib/mcollective/util.rb
lib/mcollective/facts/base.rb
lib/mcollective/security/base.rb
lib/mcollective/config.rb
lib/mcollective/matcher.rb
lib/mcollective/unix_daemon.rb
lib/mcollective/pluginpackager.rb
lib/mcollective/security.rb
lib/mcollective/pluginpackager/standard_definition.rb
lib/mcollective/pluginpackager/agent_definition.rb
lib/mcollective/runnerstats.rb
lib/mcollective/connector.rb
lib/mcollective/windows_daemon.rb
lib/mcollective/logger.rb
lib/mcollective.rb

The Marionette Collective

Framework to build and run Systems Administration agents running on a publish/subscribe middleware system. The system allows you to treat your network as the only true source of the state of your platform via discovery agents and allow you to run agents matching discovery criteria.

For an overview of the idea behind this and what it enables please see:

  http://www.devco.net/archives/2009/10/18/middleware_for_systems_administration.php

Methods

version  

Classes and Modules

Module MCollective::Connector
Module MCollective::Facts
Module MCollective::Logger
Module MCollective::Matcher
Module MCollective::PluginManager
Module MCollective::PluginPackager
Module MCollective::RPC
Module MCollective::Registration
Module MCollective::Security
Module MCollective::Util
Class MCollective::Agents
Class MCollective::Application
Class MCollective::Applications
Class MCollective::Client
Class MCollective::Config
Class MCollective::DDLValidationError
Class MCollective::InvalidRPCData
Class MCollective::Log
Class MCollective::Message
Class MCollective::MissingRPCData
Class MCollective::MsgDoesNotMatchRequestID
Class MCollective::MsgTTLExpired
Class MCollective::NotTargettedAtUs
Class MCollective::Optionparser
Class MCollective::RPCAborted
Class MCollective::RPCError
Class MCollective::Runner
Class MCollective::RunnerStats
Class MCollective::SSL
Class MCollective::SecurityValidationFailed
Class MCollective::Shell
Class MCollective::UnixDaemon
Class MCollective::UnknownRPCAction
Class MCollective::UnknownRPCError
Class MCollective::WindowsDaemon

Constants

VERSION = "@DEVELOPMENT_VERSION@"

Public Class methods

[Source]

    # File lib/mcollective.rb, line 67
67:   def self.version
68:     VERSION
69:   end

[Validate]

mcollective-2.0.0/ext/0000755000175000017500000000000011747546503013641 5ustar jonasjonasmcollective-2.0.0/ext/osx/0000755000175000017500000000000011747546503014452 5ustar jonasjonasmcollective-2.0.0/ext/osx/README0000644000175000017500000000132011747546503015326 0ustar jonasjonasThis script will automatically build Mac packages based on your source tarball. The only argument is the path to the untarred source directory. It automatically determines the MCollective version so it will only work with 0.4.9 and forward. It loads mcollective in ruby so it requires that the stomp rubygem be installed. I could just grep the mcollective.rb script but I wanted it to be future proof. It also requires XCode be installed to have PackageMaker available. It sets the server.cfg file daemonize setting to 0 since my launchd plist will handle that. I also have launchd set to restart mcollectived if it stops running. Let me know if you run in to trouble with it. Carl Caum - carl _at_ carlcaum.com mcollective-2.0.0/ext/osx/bldmacpkg0000644000175000017500000001144411747546503016325 0ustar jonasjonas#!/bin/bash MPATH='' BETCDIR='/etc/mcollective' BRUBYDIR='/Library/Ruby/Site/1.8' BSBINDIR='/usr/sbin' BBINDIR='/usr/bin' BLIBEXECDIR='/usr/libexec/mcollective' BDOCDIR='/usr/share/doc/mcollective' BLAUNCHDIR='/Library/LaunchDaemons' if [ -z $1 ]; then echo 'Please give the path to the MCollective source directory' exit 1 else MPATH=$1 fi function msg_stomp { echo 'It is recommended to install stomp on this system using ruby gems' exit 2 } function msg_xcode { echo 'It is required to have the latest XCode installed' exit 3 } #Make sure we have stomp so we can load mcollective /usr/bin/ruby < $tmpdir/$BLAUNCHDIR/org.marionette-collective.mcollective.plist < EnvironmentVariables PATH /sbin:/usr/sbin:/bin:/usr/bin RUBYLIB /Library/Ruby/Site/1.8 Label org.marionette-collective.mcollective OnDemand KeepAlive ProgramArguments /usr/sbin/mcollectived --config=/etc/mcollective/server.cfg RunAtLoad ServiceDescription MCollective Server ServiceIPC EOF #launchd complains if the permissions aren't right chmod 644 $tmpdir/$BLAUNCHDIR/org.marionette-collective.mcollective.plist #Make our Packages. This requires XCode be installed /Developer/Applications/Utilities/PackageMaker.app/Contents/MacOS/PackageMaker -r $tmpdir --version $mcversion --title "MCollective" -l / -o MCollective_$mcversion.pkg -i org.marionette-collective.mcollective /Developer/Applications/Utilities/PackageMaker.app/Contents/MacOS/PackageMaker -r $common_tmpdir --version $mcversion --title "MCollective Common" -l / -o MCollective-Common_$mcversion.pkg -i org.marionette-collective.mcollective-common /Developer/Applications/Utilities/PackageMaker.app/Contents/MacOS/PackageMaker -r $client_tmpdir --version $mcversion --title "MCollective Client" -l / -o MCollective-Client_$mcversion.pkg -i org.marionette-collective.mcollective-client #Clean up rm -rf $tmpdir rm -rf $common_tmpdir rm -rf $client_tmpdir mcollective-2.0.0/ext/mc-rpc-restserver.rb0000755000175000017500000000153211747546503017555 0ustar jonasjonas#!/usr/bin/env ruby # A very simple demonstration of writing a REST server # for Simple RPC clients that takes requests over HTTP # and returns results as JSON structures. require 'rubygems' require 'sinatra' require 'mcollective' require 'json' include MCollective::RPC # http:///mcollective/rpctest/echo/msg=hello%20world # # Creates a new Simple RPC client for the 'rpctest' agent, calls # the echo action with a message 'hello world'. # # Returns all the answers as a JSON data block get '/mcollective/:agent/:action/*' do mc = rpcclient(params[:agent]) mc.discover arguments = {} # split up the wildcard params into key=val pairs and # build the arguments hash params[:splat].each do |arg| arguments[$1.to_sym] = $2 if arg =~ /^(.+?)=(.+)$/ end JSON.dump(mc.send(params[:action], arguments).map{|r| r.results}) end mcollective-2.0.0/ext/Makefile0000644000175000017500000000215111747546503015300 0ustar jonasjonas#!/usr/bin/make -f DESTDIR= build: clean: install: install-bin install-lib install-conf install-plugins install-doc install-bin: install -d $(DESTDIR)/usr/sbin install -d $(DESTDIR)/usr/bin cp bin/mc-* $(DESTDIR)/usr/sbin cp bin/mco $(DESTDIR)/usr/bin cp bin/mcollectived $(DESTDIR)/usr/sbin/mcollectived install-lib: install -d $(DESTDIR)/usr/lib/ruby/1.8/ cp -a lib/* $(DESTDIR)/usr/lib/ruby/1.8/ install-conf: install -d $(DESTDIR)/etc/mcollective/ install -d $(DESTDIR)/etc/init.d cp -r etc/* $(DESTDIR)/etc/mcollective/ cp mcollective.init $(DESTDIR)/etc/init.d/mcollective rm $(DESTDIR)/etc/mcollective/ssl/PLACEHOLDER rm $(DESTDIR)/etc/mcollective/ssl/clients/PLACEHOLDER install-plugins: install -d $(DESTDIR)/usr/share/mcollective/ cp -a plugins $(DESTDIR)/usr/share/mcollective/ install-doc: install -d $(DESTDIR)/usr/share/doc/ cp -a doc $(DESTDIR)/usr/share/doc/mcollective uninstall: rm -f $(DESTDIR)/usr/sbin/mcollectived rm -rf $(DESTDIR)/usr/lib/ruby/1.8/mcollective* rm -rf $(DESTDIR)/usr/share/mcollective rm -rf $(DESTDIR)/etc/mcollective .PHONY: build clean install uninstall mcollective-2.0.0/ext/redhat/0000755000175000017500000000000011747546503015110 5ustar jonasjonasmcollective-2.0.0/ext/redhat/mcollective.init0000755000175000017500000000617311747546503020315 0ustar jonasjonas#!/bin/sh # # mcollective Application Server for STOMP based agents # # chkconfig: - 24 76 # # description: mcollective lets you build powerful Stomp compatible middleware clients in ruby without having to worry too # much about all the setup and management of a Stomp connection, it also provides stats, logging and so forth # as a bonus. # ### BEGIN INIT INFO # Provides: mcollective # Required-Start: $remote_fs # Required-Stop: $remote_fs # Short-Description: Start daemon at boot time # Description: Enable service provided by daemon. ### END INIT INFO mcollectived="/usr/sbin/mcollectived" pidfile="/var/run/mcollectived.pid" if [ -d /var/lock/subsys ]; then # RedHat/CentOS/etc who use subsys lockfile="/var/lock/subsys/mcollective" else # The rest of them lockfile="/var/lock/mcollective" fi # Check that binary exists if ! [ -f $mcollectived ]; then echo "mcollectived binary not found" exit 5 fi # Source function library. . /etc/init.d/functions if [ -f /etc/sysconfig/mcollective ]; then . /etc/sysconfig/mcollective fi # Determine if we can use the -p option to daemon, killproc, and status. # RHEL < 5 can't. if status | grep -q -- '-p' 2>/dev/null; then daemonopts="--pidfile $pidfile" pidopts="-p $pidfile" fi start() { echo -n "Starting mcollective: " # Only try to start if not already started if ! rh_status_q; then daemon ${daemonopts} ${mcollectived} --pid=${pidfile} --config="/etc/mcollective/server.cfg" fi # This will be 0 if mcollective is already running RETVAL=$? echo [ $RETVAL -eq 0 ] && touch ${lockfile} return $RETVAL } stop() { echo -n "Shutting down mcollective: " # If running, try to stop it if rh_status_q; then killproc ${pidopts} -d 10 ${mcollectived} else # Non-zero status either means lockfile and pidfile need cleanup (1 and 2) # or the process is already stopped (3), so we can just call true to # trigger the cleanup that happens below. true fi RETVAL=$? echo [ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile} return $RETVAL } restart() { stop start } reload_agents() { echo -n "Reloading mcollective agents: " killproc ${pidopts} ${mcollectived} -USR1 RETVAL=$? echo return $RETVAL } reload_loglevel() { echo -n "Cycling mcollective logging level: " killproc ${pidopts} ${mcollectived} -USR2 RETVAL=$? echo return $RETVAL } rh_status() { status ${pidopts} ${mcollectived} RETVAL=$? return $RETVAL } rh_status_q() { rh_status >/dev/null 2>&1 } # See how we were called. case "$1" in start) start ;; stop) stop ;; restart) restart ;; condrestart) rh_status_q || exit 0 restart ;; reload-agents) reload_agents ;; reload-loglevel) reload_loglevel ;; status) rh_status ;; *) echo "Usage: mcollectived {start|stop|restart|condrestart|reload-agents|reload-loglevel|status}" RETVAL=2 ;; esac exit $RETVAL mcollective-2.0.0/ext/redhat/mcollective.spec0000644000175000017500000001020511747546503020270 0ustar jonasjonas%{!?ruby_sitelib: %global ruby_sitelib %(ruby -rrbconfig -e "puts Config::CONFIG['sitelibdir']")} %define release %{rpm_release}%{?dist} Summary: Application Server for hosting Ruby code on any capable middleware Name: mcollective Version: %{version} Release: %{release} Group: System Environment/Daemons License: ASL 2.0 URL: http://puppetlabs.com/mcollective/introduction/ Source0: http://downloads.puppetlabs.com/mcollective/%{name}-%{version}.tgz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: ruby BuildRequires: ruby(abi) = 1.8 Requires: mcollective-common = %{version}-%{release} Packager: R.I.Pienaar BuildArch: noarch %package common Summary: Common libraries for the mcollective clients and servers Group: System Environment/Libraries Requires: ruby Requires: ruby(abi) = 1.8 Requires: rubygems Requires: rubygem(stomp) %description common The Marionette Collective: Common libraries for the mcollective clients and servers %package client Summary: Client tools for the mcollective Application Server Requires: mcollective-common = %{version}-%{release} Group: Applications/System %description client The Marionette Collective: Client tools for the mcollective Application Server %description The Marionette Collective: Server for the mcollective Application Server %prep %setup -q %build %install rm -rf %{buildroot} %{__install} -d -m0755 %{buildroot}/%{ruby_sitelib}/mcollective %{__install} -d -m0755 %{buildroot}%{_bindir} %{__install} -d -m0755 %{buildroot}%{_sbindir} %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/init.d %{__install} -d -m0755 %{buildroot}%{_libexecdir}/mcollective/ %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/mcollective %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/mcollective/plugin.d %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/mcollective/ssl %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/mcollective/ssl/clients %{__install} -m0755 bin/mcollectived %{buildroot}%{_sbindir}/mcollectived %{__install} -m0640 etc/server.cfg.dist %{buildroot}%{_sysconfdir}/mcollective/server.cfg %{__install} -m0644 etc/client.cfg.dist %{buildroot}%{_sysconfdir}/mcollective/client.cfg %{__install} -m0444 etc/facts.yaml.dist %{buildroot}%{_sysconfdir}/mcollective/facts.yaml %{__install} -m0444 etc/rpc-help.erb %{buildroot}%{_sysconfdir}/mcollective/rpc-help.erb %if 0%{?suse_version} %{__install} -m0755 mcollective.init %{buildroot}%{_sysconfdir}/init.d/mcollective %else %{__install} -m0755 ext/redhat/mcollective.init %{buildroot}%{_sysconfdir}/init.d/mcollective %endif cp -R lib/* %{buildroot}/%{ruby_sitelib}/ cp -R plugins/* %{buildroot}%{_libexecdir}/mcollective/ cp bin/mc-* %{buildroot}%{_sbindir}/ cp bin/mco %{buildroot}%{_bindir}/ chmod 0755 %{buildroot}%{_sbindir}/* %clean rm -rf %{buildroot} %post /sbin/chkconfig --add mcollective || : %postun if [ "$1" -ge 1 ]; then /sbin/service mcollective condrestart &>/dev/null || : fi %preun if [ "$1" = 0 ] ; then /sbin/service mcollective stop > /dev/null 2>&1 /sbin/chkconfig --del mcollective || : fi %files common %doc COPYING %{ruby_sitelib}/mcollective.rb %{ruby_sitelib}/mcollective %{_libexecdir}/mcollective/mcollective/agent %{_libexecdir}/mcollective/mcollective/audit %{_libexecdir}/mcollective/mcollective/connector %{_libexecdir}/mcollective/mcollective/facts %{_libexecdir}/mcollective/mcollective/registration %{_libexecdir}/mcollective/mcollective/security %dir %{_sysconfdir}/mcollective %dir %{_sysconfdir}/mcollective/ssl %config%{_sysconfdir}/mcollective/rpc-help.erb %files client %attr(0755, root, root)%{_sbindir}/mc-call-agent %attr(0755, root, root)%{_bindir}/mco %doc COPYING %config(noreplace)%{_sysconfdir}/mcollective/client.cfg %{_libexecdir}/mcollective/mcollective/application %{_libexecdir}/mcollective/mcollective/pluginpackager %files %doc COPYING %{_sbindir}/mcollectived %{_sysconfdir}/init.d/mcollective %config(noreplace)%{_sysconfdir}/mcollective/server.cfg %config(noreplace)%{_sysconfdir}/mcollective/facts.yaml %dir %{_sysconfdir}/mcollective/ssl/clients %config(noreplace)%{_sysconfdir}/mcollective/plugin.d %changelog * Tue Nov 03 2009 R.I.Pienaar - First release mcollective-2.0.0/ext/vim/0000755000175000017500000000000011747546503014434 5ustar jonasjonasmcollective-2.0.0/ext/vim/mcollective_ddl.snippets0000644000175000017500000000351211747546503021355 0ustar jonasjonas# Snippets for use with VIM and http://www.vim.org/scripts/script.php?script_id=2540 # # These snippets help you write Agent DDLs. Install the VIM Snippets system # and copy this to your snippets directory. # # Create a file .vim/ftdetect/mcollective_ddl.vim with the following: # # au BufRead,BufNewFile *.ddl setfiletype mcollective_ddl # # Your file type should now be correctly set automatically and editing # DDLs should be easier. # # Please contact R.I.Pienaar for additions and feedback, snippet meta metadata :name => "${1:`Filename('', 'name')`}", :description => "${2:description}", :author => "${3:`g:snips_author`}", :license => "${4:license}", :version => "${5:version}", :url => "${6:homepage}", :timeout => ${7:run timeout} ${8} snippet action action "${1:action name}", :description => "${2:action description}" do ${3} end snippet input String input :${1:input name}, :prompt => "${2:prompt when asking for information}", :description => "${3:description of the input}", :type => :string, :validation => '${4:^.+$}', :optional => ${5:false}, :maxlength => ${6:20} ${7} snippet input List input :${1:input name}, :prompt => "${2:prompt when asking for information}", :description => "${3:description of the input}", :type => :list, :optional => ${4:false}, :list => [${5:list members}] ${6} snippet output output ${1:output name}, :description => "${2:description of this output data}", :display_as => "${3:what do display}" ${4} snippet display Always display :always snippet display Only OK results display :ok snippet display Only failed results display :failed mcollective-2.0.0/ext/vim/_.snippets0000644000175000017500000000117311747546503016443 0ustar jonasjonassnippet mcagent module MCollective module Agent class ${1:Agentname} "${2:`Filename('', 'name')`}", :description => "${3:description}", :author => "${4:`g:snips_author`}", :license => "${5:license}", :version => "${6:version}", :url => "${7:homepage}", :timeout => ${8:run timeout} action "${9:action name}" do ${10} end end end end mcollective-2.0.0/ext/openbsd/0000755000175000017500000000000011747546503015273 5ustar jonasjonasmcollective-2.0.0/ext/openbsd/README0000644000175000017500000000021311747546503016147 0ustar jonasjonasThese files are meant to be places in /usr/port/mystuff/sysutils and then follow the OpenBSD guidelines for "custom" ports. Happy hacking mcollective-2.0.0/ext/openbsd/port-files/0000755000175000017500000000000011747546503017357 5ustar jonasjonasmcollective-2.0.0/ext/openbsd/port-files/mcollective/0000755000175000017500000000000011747546503021665 5ustar jonasjonasmcollective-2.0.0/ext/openbsd/port-files/mcollective/Makefile0000644000175000017500000000066311747546503023332 0ustar jonasjonasPKG_ARCH= * COMMENT= The Marionette Collective DISTNAME= mcollective-0.4.9 CATEGORIES= sysutils HOMEPAGE= http://code.google.com/p/mcollective/ MASTER_SITES= http://mcollective.googlecode.com/files/ EXTRACT_SUFX= .tgz # GFDL PERMIT_PACKAGE_CDROM= Yes PERMIT_PACKAGE_FTP= Yes PERMIT_DISTFILES_CDROM= Yes PERMIT_DISTFILES_FTP= Yes NO_BUILD= Yes NO_REGRESS= Yes # makefile is in ext/ MAKE_FILE=ext/Makefile .include mcollective-2.0.0/ext/openbsd/port-files/mcollective/patches/0000755000175000017500000000000011747546503023314 5ustar jonasjonasmcollective-2.0.0/ext/openbsd/port-files/mcollective/patches/patch-etc_server_cfg_dist0000644000175000017500000000047211747546503030342 0ustar jonasjonas$OpenBSD$ --- etc/server.cfg.dist.orig Thu Jun 24 15:57:17 2010 +++ etc/server.cfg.dist Thu Jun 24 15:57:25 2010 @@ -1,5 +1,5 @@ topicprefix = /topic/mcollective -libdir = /usr/libexec/mcollective +libdir = /usr/local/share/mcollective/plugins logfile = /var/log/mcollective.log loglevel = info daemonize = 1 mcollective-2.0.0/ext/openbsd/port-files/mcollective/patches/patch-ext_Makefile0000644000175000017500000000345111747546503026734 0ustar jonasjonas$OpenBSD$ --- ext/Makefile.orig Tue Aug 24 16:26:59 2010 +++ ext/Makefile Tue Aug 24 16:27:08 2010 @@ -1,6 +1,5 @@ #!/usr/bin/make -f -DESTDIR= build: @@ -9,34 +8,30 @@ clean: install: install-bin install-lib install-conf install-plugins install-doc install-bin: - install -d $(DESTDIR)/usr/sbin - cp mc-* $(DESTDIR)/usr/sbin - cp mcollectived.rb $(DESTDIR)/usr/sbin/mcollectived + install -d $(PREFIX)/sbin + cp mc-* $(PREFIX)/sbin + cp mcollectived.rb $(PREFIX)/sbin/mcollectived install-lib: - install -d $(DESTDIR)/usr/lib/ruby/1.8/ - cp -a lib/* $(DESTDIR)/usr/lib/ruby/1.8/ + install -d $(PREFIX)/lib/ruby/1.8/ + cp -R lib/* $(PREFIX)/lib/ruby/1.8/ install-conf: - install -d $(DESTDIR)/etc/mcollective/ - install -d $(DESTDIR)/etc/init.d - cp -r etc/* $(DESTDIR)/etc/mcollective/ - cp mcollective.init $(DESTDIR)/etc/init.d/mcollective - rm $(DESTDIR)/etc/mcollective/ssl/PLACEHOLDER - rm $(DESTDIR)/etc/mcollective/ssl/clients/PLACEHOLDER + install -d $(PREFIX)/share/examples/mcollective/ + cp -r etc/* $(PREFIX)/share/examples/mcollective/ install-plugins: - install -d $(DESTDIR)/usr/share/mcollective/ - cp -a plugins $(DESTDIR)/usr/share/mcollective/ + install -d $(PREFIX)/share/mcollective/ + cp -R plugins $(PREFIX)/share/mcollective/ install-doc: - install -d $(DESTDIR)/usr/share/doc/ - cp -a doc $(DESTDIR)/usr/share/doc/mcollective + install -d $(PREFIX)/share/doc/ + cp -R doc $(PREFIX)/share/doc/mcollective uninstall: - rm -f $(DESTDIR)/usr/sbin/mcollectived - rm -rf $(DESTDIR)/usr/lib/ruby/1.8/mcollective* - rm -rf $(DESTDIR)/usr/share/mcollective - rm -rf $(DESTDIR)/etc/mcollective + rm -f $(PREFIX)/sbin/mcollectived + rm -rf $(PREFIX)/lib/ruby/1.8/mcollective* + rm -rf $(PREFIX)/share/mcollective + rm -rf $(PREFIX)/etc/mcollective .PHONY: build clean install uninstall mcollective-2.0.0/ext/openbsd/port-files/mcollective/pkg/0000755000175000017500000000000011747546503022446 5ustar jonasjonasmcollective-2.0.0/ext/openbsd/port-files/mcollective/pkg/PLIST0000644000175000017500000002322211747546503023265 0ustar jonasjonas@comment $OpenBSD$ lib/ruby/ lib/ruby/1.8/ lib/ruby/1.8/mcollective/ lib/ruby/1.8/mcollective.rb lib/ruby/1.8/mcollective/agents.rb lib/ruby/1.8/mcollective/client.rb lib/ruby/1.8/mcollective/config.rb lib/ruby/1.8/mcollective/connector/ lib/ruby/1.8/mcollective/connector.rb lib/ruby/1.8/mcollective/connector/base.rb lib/ruby/1.8/mcollective/facts/ lib/ruby/1.8/mcollective/facts.rb lib/ruby/1.8/mcollective/facts/base.rb lib/ruby/1.8/mcollective/log.rb lib/ruby/1.8/mcollective/monkey_patches.rb lib/ruby/1.8/mcollective/optionparser.rb lib/ruby/1.8/mcollective/pluginmanager.rb lib/ruby/1.8/mcollective/registration/ lib/ruby/1.8/mcollective/registration.rb lib/ruby/1.8/mcollective/registration/base.rb lib/ruby/1.8/mcollective/request.rb lib/ruby/1.8/mcollective/rpc/ lib/ruby/1.8/mcollective/rpc.rb lib/ruby/1.8/mcollective/rpc/agent.rb lib/ruby/1.8/mcollective/rpc/audit.rb lib/ruby/1.8/mcollective/rpc/client.rb lib/ruby/1.8/mcollective/rpc/ddl.rb lib/ruby/1.8/mcollective/rpc/helpers.rb lib/ruby/1.8/mcollective/rpc/progress.rb lib/ruby/1.8/mcollective/rpc/reply.rb lib/ruby/1.8/mcollective/rpc/request.rb lib/ruby/1.8/mcollective/rpc/result.rb lib/ruby/1.8/mcollective/rpc/stats.rb lib/ruby/1.8/mcollective/runner.rb lib/ruby/1.8/mcollective/runnerstats.rb lib/ruby/1.8/mcollective/security/ lib/ruby/1.8/mcollective/security.rb lib/ruby/1.8/mcollective/security/base.rb lib/ruby/1.8/mcollective/util.rb sbin/mc-call-agent sbin/mc-controller sbin/mc-facts sbin/mc-find-hosts sbin/mc-inventory sbin/mc-ping sbin/mc-rpc sbin/mcollectived share/doc/mcollective/ share/doc/mcollective/classes/ share/doc/mcollective/classes/MCollective/ share/doc/mcollective/classes/MCollective.html share/doc/mcollective/classes/MCollective/Agent/ share/doc/mcollective/classes/MCollective/Agent.html share/doc/mcollective/classes/MCollective/Agent/Discovery.html share/doc/mcollective/classes/MCollective/Agent/Rpcutil.html share/doc/mcollective/classes/MCollective/Agents.html share/doc/mcollective/classes/MCollective/Client.html share/doc/mcollective/classes/MCollective/Config.html share/doc/mcollective/classes/MCollective/Connector/ share/doc/mcollective/classes/MCollective/Connector.html share/doc/mcollective/classes/MCollective/Connector/Base.html share/doc/mcollective/classes/MCollective/Connector/Stomp.html share/doc/mcollective/classes/MCollective/DDLValidationError.html share/doc/mcollective/classes/MCollective/Facts/ share/doc/mcollective/classes/MCollective/Facts.html share/doc/mcollective/classes/MCollective/Facts/Base.html share/doc/mcollective/classes/MCollective/Facts/Yaml.html share/doc/mcollective/classes/MCollective/InvalidRPCData.html share/doc/mcollective/classes/MCollective/Log.html share/doc/mcollective/classes/MCollective/MissingRPCData.html share/doc/mcollective/classes/MCollective/MsgDoesNotMatchRequestID.html share/doc/mcollective/classes/MCollective/NotTargettedAtUs.html share/doc/mcollective/classes/MCollective/Optionparser.html share/doc/mcollective/classes/MCollective/PluginManager.html share/doc/mcollective/classes/MCollective/RPC/ share/doc/mcollective/classes/MCollective/RPC.html share/doc/mcollective/classes/MCollective/RPC/Agent.html share/doc/mcollective/classes/MCollective/RPC/Audit.html share/doc/mcollective/classes/MCollective/RPC/Client.html share/doc/mcollective/classes/MCollective/RPC/DDL.html share/doc/mcollective/classes/MCollective/RPC/Helpers.html share/doc/mcollective/classes/MCollective/RPC/Logfile.html share/doc/mcollective/classes/MCollective/RPC/Progress.html share/doc/mcollective/classes/MCollective/RPC/Reply.html share/doc/mcollective/classes/MCollective/RPC/Request.html share/doc/mcollective/classes/MCollective/RPC/Result.html share/doc/mcollective/classes/MCollective/RPC/Stats.html share/doc/mcollective/classes/MCollective/RPCAborted.html share/doc/mcollective/classes/MCollective/RPCError.html share/doc/mcollective/classes/MCollective/Registration/ share/doc/mcollective/classes/MCollective/Registration.html share/doc/mcollective/classes/MCollective/Registration/Agentlist.html share/doc/mcollective/classes/MCollective/Registration/Base.html share/doc/mcollective/classes/MCollective/Request.html share/doc/mcollective/classes/MCollective/Runner.html share/doc/mcollective/classes/MCollective/RunnerStats.html share/doc/mcollective/classes/MCollective/Security/ share/doc/mcollective/classes/MCollective/Security.html share/doc/mcollective/classes/MCollective/Security/Base.html share/doc/mcollective/classes/MCollective/Security/Psk.html share/doc/mcollective/classes/MCollective/Security/Ssl.html share/doc/mcollective/classes/MCollective/UnknownRPCAction.html share/doc/mcollective/classes/MCollective/UnknownRPCError.html share/doc/mcollective/classes/MCollective/Util.html share/doc/mcollective/classes/Symbol.html share/doc/mcollective/created.rid share/doc/mcollective/files/ share/doc/mcollective/files/ext/ share/doc/mcollective/files/ext/ec2demo/ share/doc/mcollective/files/ext/ec2demo/rightscale_rb.html share/doc/mcollective/files/ext/ec2demo/start-mcollective-demo_rb.html share/doc/mcollective/files/ext/mc-rpc-restserver_rb.html share/doc/mcollective/files/lib/ share/doc/mcollective/files/lib/mcollective/ share/doc/mcollective/files/lib/mcollective/agents_rb.html share/doc/mcollective/files/lib/mcollective/client_rb.html share/doc/mcollective/files/lib/mcollective/config_rb.html share/doc/mcollective/files/lib/mcollective/connector/ share/doc/mcollective/files/lib/mcollective/connector/base_rb.html share/doc/mcollective/files/lib/mcollective/connector_rb.html share/doc/mcollective/files/lib/mcollective/facts/ share/doc/mcollective/files/lib/mcollective/facts/base_rb.html share/doc/mcollective/files/lib/mcollective/facts_rb.html share/doc/mcollective/files/lib/mcollective/log_rb.html share/doc/mcollective/files/lib/mcollective/monkey_patches_rb.html share/doc/mcollective/files/lib/mcollective/optionparser_rb.html share/doc/mcollective/files/lib/mcollective/pluginmanager_rb.html share/doc/mcollective/files/lib/mcollective/registration/ share/doc/mcollective/files/lib/mcollective/registration/base_rb.html share/doc/mcollective/files/lib/mcollective/registration_rb.html share/doc/mcollective/files/lib/mcollective/request_rb.html share/doc/mcollective/files/lib/mcollective/rpc/ share/doc/mcollective/files/lib/mcollective/rpc/agent_rb.html share/doc/mcollective/files/lib/mcollective/rpc/audit_rb.html share/doc/mcollective/files/lib/mcollective/rpc/client_rb.html share/doc/mcollective/files/lib/mcollective/rpc/ddl_rb.html share/doc/mcollective/files/lib/mcollective/rpc/helpers_rb.html share/doc/mcollective/files/lib/mcollective/rpc/progress_rb.html share/doc/mcollective/files/lib/mcollective/rpc/reply_rb.html share/doc/mcollective/files/lib/mcollective/rpc/request_rb.html share/doc/mcollective/files/lib/mcollective/rpc/result_rb.html share/doc/mcollective/files/lib/mcollective/rpc/stats_rb.html share/doc/mcollective/files/lib/mcollective/rpc_rb.html share/doc/mcollective/files/lib/mcollective/runner_rb.html share/doc/mcollective/files/lib/mcollective/runnerstats_rb.html share/doc/mcollective/files/lib/mcollective/security/ share/doc/mcollective/files/lib/mcollective/security/base_rb.html share/doc/mcollective/files/lib/mcollective/security_rb.html share/doc/mcollective/files/lib/mcollective/util_rb.html share/doc/mcollective/files/lib/mcollective_rb.html share/doc/mcollective/files/mcollectived_rb.html share/doc/mcollective/files/plugins/ share/doc/mcollective/files/plugins/mcollective/ share/doc/mcollective/files/plugins/mcollective/agent/ share/doc/mcollective/files/plugins/mcollective/agent/discovery_rb.html share/doc/mcollective/files/plugins/mcollective/agent/rpcutil_rb.html share/doc/mcollective/files/plugins/mcollective/audit/ share/doc/mcollective/files/plugins/mcollective/audit/logfile_rb.html share/doc/mcollective/files/plugins/mcollective/connector/ share/doc/mcollective/files/plugins/mcollective/connector/stomp_rb.html share/doc/mcollective/files/plugins/mcollective/facts/ share/doc/mcollective/files/plugins/mcollective/facts/yaml_rb.html share/doc/mcollective/files/plugins/mcollective/registration/ share/doc/mcollective/files/plugins/mcollective/registration/agentlist_rb.html share/doc/mcollective/files/plugins/mcollective/security/ share/doc/mcollective/files/plugins/mcollective/security/psk_rb.html share/doc/mcollective/files/plugins/mcollective/security/ssl_rb.html share/doc/mcollective/fr_class_index.html share/doc/mcollective/fr_file_index.html share/doc/mcollective/fr_method_index.html share/doc/mcollective/index.html share/doc/mcollective/rdoc-style.css share/examples/mcollective/ share/examples/mcollective/client.cfg.dist share/examples/mcollective/facts.yaml.dist share/examples/mcollective/rpc-help.erb share/examples/mcollective/server.cfg.dist share/examples/mcollective/ssl/ share/examples/mcollective/ssl/PLACEHOLDER share/examples/mcollective/ssl/clients/ share/examples/mcollective/ssl/clients/PLACEHOLDER share/mcollective/ share/mcollective/plugins/ share/mcollective/plugins/mcollective/ share/mcollective/plugins/mcollective/agent/ share/mcollective/plugins/mcollective/agent/discovery.rb share/mcollective/plugins/mcollective/agent/rpcutil.ddl share/mcollective/plugins/mcollective/agent/rpcutil.rb share/mcollective/plugins/mcollective/audit/ share/mcollective/plugins/mcollective/audit/logfile.rb share/mcollective/plugins/mcollective/connector/ share/mcollective/plugins/mcollective/connector/stomp.rb share/mcollective/plugins/mcollective/facts/ share/mcollective/plugins/mcollective/facts/yaml.rb share/mcollective/plugins/mcollective/registration/ share/mcollective/plugins/mcollective/registration/agentlist.rb share/mcollective/plugins/mcollective/security/ share/mcollective/plugins/mcollective/security/psk.rb share/mcollective/plugins/mcollective/security/ssl.rb mcollective-2.0.0/ext/openbsd/port-files/mcollective/pkg/MESSAGE0000644000175000017500000000034511747546503023457 0ustar jonasjonasIf you wish to have mcollective started automatically at boot time, simply add the follow lines to /etc/rc.local: if [ -x ${PREFIX}/sbin/mcollectived ]; then echo -n ' mcollective'; ${PREFIX}/sbin/mcollectived fi mcollective-2.0.0/ext/openbsd/port-files/mcollective/pkg/DESCR0000644000175000017500000000017511747546503023234 0ustar jonasjonasThe Marionette Collective aka. mcollective is a framework to build server orchestration or parallel job execution systems. mcollective-2.0.0/ext/openbsd/port-files/mcollective/distinfo0000644000175000017500000000044511747546503023432 0ustar jonasjonasMD5 (mcollective-0.4.9.tgz) = EUPVstRRmazUyRU8sHZCDw== RMD160 (mcollective-0.4.9.tgz) = GC4vNkZXfmwUs4nItpq0/nl89bA= SHA1 (mcollective-0.4.9.tgz) = dCY1wibGkj6WOyntMhf0OZ1ajt4= SHA256 (mcollective-0.4.9.tgz) = ThTHJxAZdMAX43y82rWtbpSapg/TsT4qo/1icPcVMc8= SIZE (mcollective-0.4.9.tgz) = 154953 mcollective-2.0.0/ext/action_helpers/0000755000175000017500000000000011747546503016640 5ustar jonasjonasmcollective-2.0.0/ext/action_helpers/python/0000755000175000017500000000000011747546503020161 5ustar jonasjonasmcollective-2.0.0/ext/action_helpers/python/romke/0000755000175000017500000000000011747546503021276 5ustar jonasjonasmcollective-2.0.0/ext/action_helpers/python/romke/test.py0000644000175000017500000000325511747546503022634 0ustar jonasjonas#!/bin/env python # -*- coding: utf-8 -*- vim: set ts=4 et sw=4 fdm=indent : import unittest, tempfile, simplejson, os, random import mcollectiveah class TestFunctions(unittest.TestCase): def test_raise_environ(self): try: del os.environ['MCOLLECTIVE_REQUEST_FILE'] del os.environ['MCOLLECTIVE_REPLY_FILE'] except: pass self.assertRaises(mcollectiveah.MCollectiveActionNoEnv, mcollectiveah.MCollectiveAction) def test_raise_file_error(self): os.environ['MCOLLECTIVE_REQUEST_FILE'] = '/tmp/mcollectiveah-test-request.%d' % random.randrange(100000) os.environ['MCOLLECTIVE_REPLY_FILE'] = '/tmp/mcollectiveah-test-reply.%d' % random.randrange(100000) self.assertRaises(mcollectiveah.MCollectiveActionFileError, mcollectiveah.MCollectiveAction) os.unlink(os.environ['MCOLLECTIVE_REPLY_FILE']) def test_echo(self): tin = tempfile.NamedTemporaryFile(mode='w', delete=False) self.data = {'message': 'test'} simplejson.dump(self.data, tin) os.environ['MCOLLECTIVE_REQUEST_FILE'] = tin.name tin.close() tout = tempfile.NamedTemporaryFile(mode='w') os.environ['MCOLLECTIVE_REPLY_FILE'] = tout.name tout.close() mc = mcollectiveah.MCollectiveAction() mc.reply['message'] = mc.request['message'] del mc tout = open(os.environ['MCOLLECTIVE_REPLY_FILE'], 'r') data = simplejson.load(tout) tout.close() self.assertEqual(data, self.data) os.unlink(os.environ['MCOLLECTIVE_REQUEST_FILE']) os.unlink(os.environ['MCOLLECTIVE_REPLY_FILE']) if __name__ == '__main__': unittest.main() mcollective-2.0.0/ext/action_helpers/python/romke/mcollectiveah.py0000644000175000017500000000441011747546503024466 0ustar jonasjonas#!/bin/env python # -*- coding: utf-8 -*- vim: set ts=4 et sw=4 fdm=indent : import os, sys try: import simplejson except ImportError: sys.stderr.write('Unable to load simplejson python module.') sys.exit(1) class MCollectiveActionNoEnv(Exception): pass class MCollectiveActionFileError(Exception): pass class MCollectiveAction(object): def __init__(self, *args, **kwargs): try: self.infile = os.environ['MCOLLECTIVE_REQUEST_FILE'] except KeyError: raise MCollectiveActionNoEnv("No MCOLLECTIVE_REQUEST_FILE environment variable") try: self.outfile = os.environ['MCOLLECTIVE_REPLY_FILE'] except KeyError: raise MCollectiveActionNoEnv("No MCOLLECTIVE_REPLY_FILE environment variable") self.request = {} self.reply = {} self.load() def load(self): if not self.infile: return False try: infile = open(self.infile, 'r') self.request = simplejson.load(infile) infile.close() except IOError, e: raise MCollectiveActionFileError("Could not read request file `%s`: %s" % (self.infile, e)) except simplejson.JSONDecodeError, e: infile.close() raise MCollectiveActionFileError("Could not parse JSON data in file `%s`: %s", (self.infile, e)) def send(self): if not getattr(self, 'outfile', None): # if exception was raised during or before setting self.outfile return False try: outfile = open(self.outfile, 'w') simplejson.dump(self.reply, outfile) outfile.close() except IOError, e: raise MCollectiveActionFileError("Could not write reply file `%s`: %s" % (self.outfile, e)) def error(self, msg): """Prints line to STDERR that will be logged at error level in the mcollectived log file""" sys.stderr.write("%s\n" % msg) def fail(self, msg): """Logs error message and exitst with RPCAborted""" self.error(msg) sys.exit(1) def info(self, msg): """Prints line to STDOUT that will be logged at info level in the mcollectived log file""" sys.stdout.write("%s\n" % msg) def __del__(self): self.send() mcollective-2.0.0/ext/action_helpers/python/romke/README.markdown0000644000175000017500000000162211747546503024000 0ustar jonasjonasA simple helper to assist with writing MCollective actions in Python. Given an action as below:
action "echo" do
   validate :message, String

   implemented_by "/tmp/echo.py"
end
The following Python script will implement the echo action externally replying with _message_ and _timestamp_
#!/bin/env python
import mcollectiveah
import time

mc = mcollectiveah.MCollectiveAction()
mc.reply['message'] = mc.request['message']
mc.reply['timestamp'] = time.strftime("%c")
mc.reply['info'] = "some text to info log in the server"
Calling it with _mco rpc_ results in:
$ mco rpc test echo message="hello world"
Determining the amount of hosts matching filter for 2 seconds .... 1

 * [ ============================================================> ] 1 / 1


nephilim.ml.org                         : OK
    {:message=>"hello world", :time=>"Tue Mar 15 19:20:53 +0000 2011"}
mcollective-2.0.0/ext/action_helpers/python/kwilczynski/0000755000175000017500000000000011747546503022542 5ustar jonasjonasmcollective-2.0.0/ext/action_helpers/python/kwilczynski/echo.py0000644000175000017500000000050411747546503024031 0ustar jonasjonas#!/usr/bin/env python import sys import time import mcollective_action as mc if __name__ == '__main__': mc = mc.MCollectiveAction() request = mc.request() mc.message = request['data']['message'] mc.time = time.strftime('%c') mc.info("An example echo agent") sys.exit(0) # vim: set ts=4 sw=4 et : mcollective-2.0.0/ext/action_helpers/python/kwilczynski/README.markdown0000644000175000017500000000203511747546503025243 0ustar jonasjonasA simple helper to assist with writing MCollective actions in Python. Given an action as below:
action "echo" do
   validate :message, String

   implemented_by "/tmp/echo.py"
end
The following Python script will implement the echo action externally replying with _message_ and current _time_.
#!/usr/bin/env python

import sys
import time
import mcollective_action as mc

if __name__ == '__main__':
    mc = mc.MCollectiveAction()
    request = mc.request()
    mc.message = request['data']['message']
    mc.time = time.strftime('%c')
    mc.info("Some text to info log in the server")

    sys.exit(0)
Calling it with _mco rpc_ results in:
$ mco rpc test echo message="Hello World"
Determining the amount of hosts matching filter for 2 seconds .... 1

 * [ ============================================================> ] 1 / 1


host.example.com              : OK
    {:message=>"Hello World", :time=>"Tue Mar 15 19:20:53 +0000 2011"}
This implementation was successfully tested with Python 2.4 and 2.6. mcollective-2.0.0/ext/action_helpers/python/kwilczynski/mcollective_action.py0000644000175000017500000000623011747546503026760 0ustar jonasjonas#!/usr/bin/env python import os import sys class Error(Exception): pass class MissingModule(Error): pass class MissingFiles(Error): pass class MissingEnvironemntVariable(Error): pass class FileReadError(Error): pass class JSONParsingError(Error): pass try: import simplejson as json except ImportError: raise MissingModule('Unable to load JSON module. Missing module?') class MCollectiveAction(object): _environment_variables = [ 'MCOLLECTIVE_REQUEST_FILE', 'MCOLLECTIVE_REPLY_FILE' ] def __init__(self): self._info = sys.__stdout__ self._error = sys.__stderr__ for entry in '_reply', '_request': self.__dict__[entry] = {} self._arguments = sys.argv[1:] if len(self._arguments) < 2: try: for variable in self._environment_variables: self._arguments.append(os.environ[variable]) except KeyError: raise MissingEnvironemntVariable("Environment variable `%s' " "is not set." % variable) self._request_file, self._reply_file = self._arguments if len(self._request_file) == 0 or len(self._reply_file) == 0: raise MissingFiles("Both request and reply files have to be set.") def __setattr__(self, name, value): if name.startswith('_'): object.__setattr__(self, name, value) else: self.__dict__['_reply'][name] = value def __getattr__(self, name): if name.startswith('_'): return self.__dict__.get(name, None) else: return self.__dict__['_reply'].get(name, None) def __del__(self): if self._reply: try: file = open(self._reply_file, 'w') json.dump(self._reply, file) file.close() except IOError, error: raise FileReadError("Unable to open reply file `%s': %s" % (self._reply_file, error)) def info(self, message): print >> self._info, message self._info.flush() def error(self, message): print >> self._error, message self._error.flush() def fail(self, message, exit_code=1): self.error(message) sys.exit(exit_code) def reply(self): return self._reply def request(self): if self._request: return self._request else: try: file = open(self._request_file, 'r') self._request = json.load(file) file.close() except IOError, error: raise FileReadError("Unable to open request file `%s': %s" % (self._request_file, error)) except json.JSONDecodeError, error: raise JSONParsingError("An error occurred during parsing of " "the JSON data in the file `%s': %s" % (self._request_file, error)) file.close() return self._request # vim: set ts=4 sw=4 et : mcollective-2.0.0/ext/action_helpers/php/0000755000175000017500000000000011747546503017427 5ustar jonasjonasmcollective-2.0.0/ext/action_helpers/php/mcollective_action.php0000644000175000017500000000326411747546503024010 0ustar jonasjonasinfile = $_ENV["MCOLLECTIVE_REQUEST_FILE"]; $this->outfile = $_ENV["MCOLLECTIVE_REPLY_FILE"]; $this->readJSON(); } function __destruct() { $this->save(); } function readJSON() { $this->request = json_decode(file_get_contents($this->infile), true); unset($this->request["data"]["process_results"]); } function save() { file_put_contents($this->outfile, json_encode($this->request["data"])); } // prints a line to STDERR that will log at error level in the // mcollectived log file function error($msg) { fwrite(STDERR, "$msg\n"); } // prints a line to STDOUT that will log at info level in the // mcollectived log file function info($msg) { fwrite(STDOUT, "$msg\n"); } // logs an error message and exits with RPCAborted function fail($msg) { $this->error($msg); exit(1); } function __get($property) { if (isSet($this->request[$property])) { return $this->request[$property]; } else { throw new Exception("No $property in request"); } } function __set($property, $value) { $this->request["data"][$property] = $value; } } ?> mcollective-2.0.0/ext/action_helpers/php/README.markdown0000644000175000017500000000157411747546503022137 0ustar jonasjonasA simple helper to assist with writing MCollective actions in PHP. Given an action as below:
action "echo" do
   validate :message, String

   implemented_by "/tmp/echo.php"
end
The following PHP script will implement the echo action externally replying with _message_ and _timestamp_
<?php
    require("mcollective_action.php");

    $mc = new MCollectiveAction();
    $mc->message = $mc->data["message"];
    $mc->timestamp = strftime("%c");
    $mc->info("some text to info log on the server");
?>
Calling it with _mco rpc_ results in:
$ mco rpc test echo message="hello world"
Determining the amount of hosts matching filter for 2 seconds .... 1

 * [ ============================================================> ] 1 / 1


nephilim.ml.org                         : OK
    {:message=>"hello world", :time=>"Tue Mar 15 19:20:53 +0000 2011"}
mcollective-2.0.0/ext/action_helpers/perl/0000755000175000017500000000000011747546503017602 5ustar jonasjonasmcollective-2.0.0/ext/action_helpers/perl/Makefile.PL0000644000175000017500000000022011747546503021546 0ustar jonasjonas#!perl use strict; use ExtUtils::MakeMaker; WriteMakefile( NAME => "MCollective::Action", PREREQ_PM => { "JSON" => 0, }, ); mcollective-2.0.0/ext/action_helpers/perl/lib/0000755000175000017500000000000011747546503020350 5ustar jonasjonasmcollective-2.0.0/ext/action_helpers/perl/lib/MCollective/0000755000175000017500000000000011747546503022556 5ustar jonasjonasmcollective-2.0.0/ext/action_helpers/perl/lib/MCollective/Action.pm0000644000175000017500000000501211747546503024327 0ustar jonasjonaspackage MCollective::Action; use strict; use warnings; use JSON; =head1 NAME MCollective::Action - helper class for writing mcollective actions in perl =head1 SYNOPSIS In your mcollective agent action "echo" do validate :message, String implemented by "/tmp/echo.perl" end And C #!/usr/bin/env perl use strict; use MCollective::Action; my $mc = MCollective::Action->new; $mc->reply->{message} = $mc->request->{message}; $mc->reply->{timestamp} = time; $mc->info("some text to log on the server"); =head1 DESCRIPTION mcollective version 1.X introduced a mechanism for writing agent actions as external commands. This module provides a convenient api for writing them in perl which performs some of the boilerplate for you. =head2 METHODS =over =item new create a new MCollection::Action helper object =cut sub new { my $class = shift; my $self = bless { request => {}, reply => {}, }, $class; $self->_load; return $self; } =item request returns a hash reference containing the request =cut sub request { $_[0]->{request} } =item reply returns a hash reference you should populate with your reply =cut sub reply { $_[0]->{reply} } sub _load { my $self = shift; my $file = $ENV{MCOLLECTIVE_REQUEST_FILE}; open my $fh, "<$file" or die "Can't open '$file': $!"; my $json = do { local $/; <$fh> }; $self->{request} = JSON->new->decode( $json ); delete $self->request->{data}{process_results}; } sub DESTROY { my $self = shift; $self->_save; } sub _save { my $self = shift; my $file = $ENV{MCOLLECTIVE_REPLY_FILE}; open my $fh, ">$file" or die "Can't open '$file': $!"; print $fh JSON->new->encode( $self->reply ); } =item info($message) report a message into the server log =cut sub info { my ($self, $message) = @_; print STDOUT $message, "\n"; } =item error($message) report an error into the server log =cut sub error { my ($self, $message) = @_; print STDERR $message, "\n"; } =item fail reports an error and exits immediately =cut sub fail { my ($self, $message) = @_; $self->error( $message ); exit 1; } 1; __END__ =back =head1 AUTHOR Richard Clamp =head1 COPYRIGHT Copyright 2011, Richard Clamp. All Rights Reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO http://docs.puppetlabs.com/mcollective/ =cut mcollective-2.0.0/ext/action_helpers/perl/.gitignore0000644000175000017500000000003111747546503021564 0ustar jonasjonasMakefile blib pm_to_blib mcollective-2.0.0/ext/action_helpers/perl/t/0000755000175000017500000000000011747546503020045 5ustar jonasjonasmcollective-2.0.0/ext/action_helpers/perl/t/basic.t0000644000175000017500000000132011747546503021307 0ustar jonasjonas#!perl use strict; use Test::More; use JSON; use File::Temp; my $class = "MCollective::Action"; use_ok( $class ); my $infile = File::Temp->new; my $outfile = File::Temp->new; $ENV{MCOLLECTIVE_REQUEST_FILE} = $infile->filename; $ENV{MCOLLECTIVE_REPLY_FILE} = $outfile->filename; print $infile JSON->new->encode({ red => "apples", blue => "moon" }); close $infile; { my $mc = $class->new; isa_ok( $mc, $class ); is( $mc->request->{red}, "apples", "apples are red" ); $mc->reply->{potato} = "chips"; } my $json = do { local $/; <$outfile> }; ok( $json, "Got some JSON" ); my $reply = JSON->new->decode( $json ); is( $reply->{potato}, "chips", "Got the reply that potato = chips" ); done_testing(); mcollective-2.0.0/ext/debian/0000755000175000017500000000000011747546503015063 5ustar jonasjonasmcollective-2.0.0/ext/debian/mcollective.init0000755000175000017500000000440311747546503020262 0ustar jonasjonas#!/bin/sh # # mcollective Application Server for STOMP based agents # # # description: mcollective lets you build powerful Stomp compatible middleware clients in ruby without having to worry too # much about all the setup and management of a Stomp connection, it also provides stats, logging and so forth # as a bonus. # ### BEGIN INIT INFO # Provides: mcollective # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start daemon at boot time # Description: Enable service provided by mcollective. ### END INIT INFO # check permissions uid=`id -u` [ $uid -gt 0 ] && { echo "You need to be root to run file" ; exit 4 ; } # PID directory pidfile="/var/run/mcollectived.pid" name="mcollective" mcollectived=/usr/sbin/mcollectived daemonopts="--pid=${pidfile} --config=/etc/mcollective/server.cfg" # Source function library. . /lib/lsb/init-functions # Check that binary exists if ! [ -f $mcollectived ] then echo "mcollectived binary not found" exit 5 fi # create pid file if it does not exist [ ! -f ${pidfile} ] && { touch ${pidfile} ; } # See how we were called. case "$1" in start) echo "Starting daemon: " $name # start the program start-stop-daemon -S -p ${pidfile} --oknodo -q -a ${mcollectived} -- ${daemonopts} [ $? = 0 ] && { exit 0 ; } || { exit 1 ; } log_success_msg "mcollective started" touch $lock ;; stop) echo "Stopping daemon: " $name start-stop-daemon -K -R 5 -s "TERM" --oknodo -q -p ${pidfile} [ $? = 0 ] && { exit 0 ; } || { exit 1 ; } log_success_msg "mcollective stopped" ;; restart) echo "Restarting daemon: " $name $0 stop sleep 2 $0 start [ $? = 0 ] && { echo "mcollective restarted" ; exit 0 ; } ;; condrestart) if [ -f $lock ]; then $0 stop # avoid race sleep 2 $0 start fi ;; status) status_of_proc -p ${pidfile} ${mcollectived} ${name} && exit 0 || exit $? ;; *) echo "Usage: mcollectived {start|stop|restart|condrestart|status}" exit 2 ;; esac mcollective-2.0.0/ext/debian/compat0000644000175000017500000000000211747546503016261 0ustar jonasjonas7 mcollective-2.0.0/ext/debian/mcollective-common.install0000644000175000017500000000117411747546503022252 0ustar jonasjonasusr/lib/ruby/1.8/* usr/lib/ruby/1.8/ etc/mcollective/rpc-help.erb etc/mcollective usr/share/mcollective/plugins/mcollective/agent usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/audit usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/connector usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/facts usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/registration usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/security usr/share/mcollective/plugins/mcollective mcollective-2.0.0/ext/debian/mcollective.install0000644000175000017500000000023111747546503020755 0ustar jonasjonasusr/sbin/mcollectived usr/sbin etc/mcollective/facts.yaml etc/mcollective etc/mcollective/server.cfg etc/mcollective etc/init.d etc/ etc/mcollective/ssl mcollective-2.0.0/ext/debian/control0000644000175000017500000000260711747546503016473 0ustar jonasjonasSource: mcollective Section: utils Priority: extra Maintainer: Riccardo Setti Build-Depends: debhelper (>= 7), dpatch, cdbs Standards-Version: 3.8.0 Homepage: http://marionette-collective.org/ Package: mcollective Architecture: all Depends: ruby (>= 1.8.1), mcollective-common (>= ${source:Version}) Description: build server orchestration or parallel job execution systems The Marionette Collective aka. mcollective is a framework to build server orchestration or parallel job execution systems. Package: mcollective-client Architecture: all Depends: ruby (>= 1.8.1), mcollective-common (>= ${source:Version}) Description: build server orchestration or parallel job execution systems The Marionette Collective aka. mcollective is a framework to build server orchestration or parallel job execution system Package: mcollective-common Architecture: all Depends: ruby (>= 1.8.1) , rubygems Description: build server orchestration or parallel job execution systems The Marionette Collective aka. mcollective is a framework to build server orchestration or parallel job execution systems. . Common files for mcollective packages. Package: mcollective-doc Architecture: all Section: doc Description: Documentation for mcollective The Marionette Collective aka. mcollective is a framework to build server orchestration or parallel job execution systems. . Documentation package. mcollective-2.0.0/ext/debian/patches/0000755000175000017500000000000011747546503016512 5ustar jonasjonasmcollective-2.0.0/ext/debian/patches/conffile.dpatch0000755000175000017500000000650711747546503021477 0ustar jonasjonas#! /bin/sh /usr/share/dpatch/dpatch-run ## conffile.dpatch by ## ## All lines beginning with `## DP:' are a description of the patch. ## DP: fix plugins dir @DPATCH@ diff -urNad mcollective-1.1.4~/etc/client.cfg.dist mcollective-1.1.4/etc/client.cfg.dist --- mcollective-1.1.4~/etc/client.cfg.dist 2011-04-06 14:13:08.829462165 -0700 +++ mcollective-1.1.4/etc/client.cfg.dist 2011-04-06 14:12:53.129384114 -0700 @@ -1,7 +1,7 @@ topicprefix = /topic/ main_collective = mcollective collectives = mcollective -libdir = /usr/libexec/mcollective +libdir = /usr/share/mcollective/plugins logger_type = console loglevel = warn diff -urNad mcollective-1.1.4~/etc/server.cfg.dist mcollective-1.1.4/etc/server.cfg.dist --- mcollective-1.1.4~/etc/server.cfg.dist 2011-04-06 14:12:30.889527230 -0700 +++ mcollective-1.1.4/etc/server.cfg.dist 2011-04-06 14:12:23.779407065 -0700 @@ -1,7 +1,7 @@ topicprefix = /topic/ main_collective = mcollective collectives = mcollective -libdir = /usr/libexec/mcollective +libdir = /usr/share/mcollective/plugins logfile = /var/log/mcollective.log loglevel = info daemonize = 1 mcollective-2.0.0/ext/debian/patches/initlsb.dpatch0000755000175000017500000000226611747546503021354 0ustar jonasjonas#! /bin/sh /usr/share/dpatch/dpatch-run ## initlsb.dpatch by on Mon, 04 Jan 2010 17:09:50 +0000. It was downloaded from http://code.google.com/p/mcollective Upstream Author: R.I.Pienaar Copyright: Copyright 2009 R.I.Pienaar License: Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. The Debian packaging is (C) 2010, Riccardo Setti and is licensed under the Apache License v2. mcollective-2.0.0/ext/debian/mcollective-doc.install0000644000175000017500000000007311747546503021524 0ustar jonasjonasusr/share/doc/mcollective/* usr/share/doc/mcollective-doc/ mcollective-2.0.0/ext/debian/rules0000755000175000017500000000142711747546503016147 0ustar jonasjonas#!/usr/bin/make -f DEB_MAKE_CLEAN_TARGET := DEB_MAKE_INSTALL_TARGET := install DESTDIR=$(CURDIR)/debian/tmp include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/rules/dpatch.mk include /usr/share/cdbs/1/class/makefile.mk DEB_MAKE_INVOKE = $(DEB_MAKE_ENVVARS) make -f ext/Makefile -C $(DEB_BUILDDIR) install/mcollective:: mv $(CURDIR)/debian/tmp/etc/mcollective/server.cfg.dist $(CURDIR)/debian/tmp/etc/mcollective/server.cfg mv $(CURDIR)/debian/tmp/etc/mcollective/client.cfg.dist $(CURDIR)/debian/tmp/etc/mcollective/client.cfg mv $(CURDIR)/debian/tmp/etc/mcollective/facts.yaml.dist $(CURDIR)/debian/tmp/etc/mcollective/facts.yaml # dh_installinit -pmcollective -o binary-fixup/mcollective:: chmod 640 $(CURDIR)/debian/mcollective/etc/mcollective/server.cfg mcollective-2.0.0/ext/debian/mcollective-client.install0000644000175000017500000000043311747546503022235 0ustar jonasjonasusr/bin/mco usr/bin/ usr/sbin/mc-* usr/sbin/ etc/mcollective/client.cfg etc/mcollective usr/share/mcollective/plugins/mcollective/application usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/pluginpackager usr/share/mcollective/plugins/mcollective mcollective-2.0.0/ext/solaris/0000755000175000017500000000000011747546503015315 5ustar jonasjonasmcollective-2.0.0/ext/solaris/mcollective.init0000755000175000017500000000354611747546503020523 0ustar jonasjonas#!/bin/sh # # mcollective Application Server for STOMP based agents # # description: mcollective lets you build powerful Stomp compatible middleware clients in ruby without having to worry too # much about all the setup and management of a Stomp connection, it also provides stats, logging and so forth # as a bonus. # RUBYLIB=/opt/csw/lib/ruby/site_ruby/1.8:$RUBYLIB export RUBYLIB mcollectived="/opt/csw/sbin/mcollectived" lock="/var/lock/mcollective" # PID directory pidfile="/var/run/mcollectived.pid" # Check that binary exists if [ ! -f $mcollectived ] then echo "mcollectived binary not found" exit 1 fi # See how we were called. case "$1" in start) if [ -f ${lock} ]; then # we were not shut down correctly if [ -s ${pidfile} ]; then kill `cat ${pidfile}` >/dev/null 2>&1 fi rm -f ${pidfile} rm -f ${lock} sleep 2 fi rm -f ${pidfile} ${mcollectived} --pid=${pidfile} --config="/etc/mcollective/server.cfg" if [ $? = 0 ]; then touch $lock exit 0 else exit 1 fi ;; stop) if [ -s ${pidfile} ]; then kill `cat ${pidfile}` >/dev/null 2>&1 fi rm -f ${pidfile} rm -f $lock ;; restart) $0 stop sleep 2 $0 start ;; condrestart) if [ -f $lock ]; then $0 stop # avoid race sleep 2 $0 start fi ;; status) if [ -f ${lock} ]; then if [ -s ${pidfile} ]; then if [ -e /proc/`cat ${pidfile}` ]; then echo "mcollectived (`cat ${pidfile}`) is running" exit 0 else echo "mcollectived (`cat ${pidfile}`) is NOT running" exit 1 fi fi else echo "mcollectived: service not started" exit 1 fi ;; force-reload) echo "not implemented" ;; *) echo "Usage: $0 {start|stop|restart|condrestart|status}" exit 1 ;; esac exit 0 mcollective-2.0.0/ext/solaris/pkginfo0000644000175000017500000000042711747546503016700 0ustar jonasjonasNAME=mcollective- build server orchestration or parallel job execution systems CATEGORY=network DESC=The Marionette Collective aka. mcollective is a framework to build server orchestration or parallel job execution systems. ARCH=all EMAIL=rudy.gevaert@ugent.be VENDOR=Puppetlabs mcollective-2.0.0/ext/solaris/setversion0000755000175000017500000000025611747546503017447 0ustar jonasjonas#! /bin/bash IN=$1 OUT=$2 SOLARIS_VERSION=`ggrep ^VERSION solaris/pkginfo | cut -d = -f2` sed 's/VERSION="none"/VERSION="'"$SOLARIS_VERSION"'"/' $IN > $OUT chmod 755 $OUT mcollective-2.0.0/ext/solaris/README0000644000175000017500000000116011747546503016173 0ustar jonasjonasBuilding -------- Requirements, you can get them from opencsw: - coreuitls (CSWcoreutils) - gmake (CSWgmake) - ggrep (CSWggrep) Just run ./build on your solaris system. Running ------- Requirements, get them from opencsw: - ruby (CSWruby) - rubygems (CSWrubygems) Run requirements - rubystomp library http://stomp.codehaus.org/Ruby+Client Up and till version 1.0.4 it is a single file. Put in /opt/csw/lib/ruby/site_ruby/1.8/ Configuration ------------- /etc/mcollective/server.cfg Put the plugins in: libdir = /opt/csw/share/mcollective/plugins Credits ------- Rudy Gevaert mcollective-2.0.0/ext/solaris/depend0000644000175000017500000000007711747546503016503 0ustar jonasjonasP CWSruby ruby P CWSrubystomp rubystomp P CSWrubygems rubygems mcollective-2.0.0/ext/solaris/preremove0000644000175000017500000000023311747546503017242 0ustar jonasjonas/usr/sbin/svcadm disable svc:network/cswmcollectived 2>/dev/null || /bin/true /usr/sbin/svccfg delete svc:network/cswmcollectived 2>/dev/null || /bin/true mcollective-2.0.0/ext/solaris/build0000755000175000017500000000603411747546503016345 0ustar jonasjonas#!/opt/csw/bin/gmake -f -d # -*- makefile -*- BUILDDIR = solaris/tmp PKG = solaris/pkg DESTDIR = ${CURDIR}/${BUILDDIR} PKGDIR = ${CURDIR}/${PKG} PKGNAME = CSWmcollective VERSION = $(shell cd ../.. ; RUBYLIB=./lib /opt/csw/bin/ruby18 -r mcollective -e 'puts MCollective::VERSION' ) # If we checked out from git: ifeq ($(VERSION),@DEVELOPMENT_VERSION@) VERSION = $(shell ggrep "PROJ_VERSION = " ../../Rakefile | cut -d' ' -f3 | sed -e 's/"//g') endif RELEASE = 1 PKGVERSION = ${VERSION}-${RELEASE}\,REV=$(shell date +%Y.%m.%d) RUBY_VERSION = 1.8 RUBY_SITE = ${DESTDIR}/opt/csw/lib/ruby/site_ruby/${RUBY_VERSION} install: # install directories ginstall -d $(DESTDIR) ginstall -g root -d $(DESTDIR)/opt ginstall -g sys -d $(DESTDIR)/var $(DESTDIR)/var/lock $(DESTDIR)/etc $(DESTDIR)/etc/opt ginstall -g bin -d $(DESTDIR)/var/opt $(DESTDIR)/var/opt/csw $(DESTDIR)/var/opt/csw/svc $(DESTDIR)/var/opt/csw/svc/manifest $(DESTDIR)/var/opt/csw/svc/manifest/network ginstall -g bin -d $(DESTDIR)/opt/csw/lib $(DESTDIR)/opt/csw/lib/svc $(DESTDIR)/opt/csw/lib/svc/method ginstall -g bin -d $(DESTDIR)/opt/csw $(DESTDIR)/opt/csw/lib $(DESTDIR)/opt/csw/sbin $(DESTDIR)/opt/csw/bin ginstall -g bin -d $(DESTDIR)/opt/csw/lib/ruby $(DESTDIR)/opt/csw/lib/ruby/site_ruby $(DESTDIR)/opt/csw/lib/ruby/site_ruby/$(RUBY_VERSION) ginstall -g bin -d $(DESTDIR)/etc/opt/csw $(DESTDIR)/etc/opt/csw/mcollective ginstall -g bin -d $(DESTDIR)/opt/csw/share $(DESTDIR)/opt/csw/share/mcollective # install binaries ginstall -g bin $(CURDIR)/../../mc-* $(DESTDIR)/opt/csw/sbin/ ginstall -g bin $(CURDIR)/../../mco $(DESTDIR)/opt/csw/sbin/ ginstall -g bin $(CURDIR)/../../mcollectived.rb $(DESTDIR)/opt/csw/sbin/mcollectived # install libraries gcp -a $(CURDIR)/../../lib/* $(RUBY_SITE)/ chgrp -R bin $(RUBY_SITE)/ # install example config files gcp -a $(CURDIR)/../../etc/* $(DESTDIR)/etc/opt/csw/mcollective/ grm $(DESTDIR)/etc/opt/csw/mcollective/ssl/PLACEHOLDER grm $(DESTDIR)/etc/opt/csw/mcollective/ssl/clients/PLACEHOLDER chgrp -R bin $(DESTDIR)/etc/opt/csw/mcollective/ # install plugins gcp -a $(CURDIR)/../../plugins $(DESTDIR)/opt/csw/share/mcollective/ # install docs #ginstall -d $(DESTDIR)/opt/csw/doc $(DESTDIR)/opt/csw/doc/mcollective/ #gcp -a $(CURDIR)/../../doc/ $(DESTDIR)/opt/cs/doc/mcollective ginstall -g bin $(CURDIR)/mcollective.init $(DESTDIR)/opt/csw/lib/svc/method/svc-cswmcollectived ginstall -g bin $(CURDIR)/cswmcollectived.xml $(DESTDIR)/var/opt/csw/svc/manifest/network (cat prototype.head; pkgproto $(DESTDIR)=/ ) > solaris/prototype mkdir $(PKGDIR) || true ginstall postinstall solaris/ ginstall postremove solaris/ ginstall preremove solaris/ ginstall pkginfo solaris/ (echo PKG=${PKGNAME} ) >> solaris/pkginfo (echo VERSION=${PKGVERSION} ) >> solaris/pkginfo (cd solaris/ ; pkgmk -o -d $(PKGDIR)) pkgtrans -s $(PKGDIR) $(CURDIR)/$(PKGNAME)-$(PKGVERSION)-`uname -s``uname -r`-all-CSW.pkg $(PKGNAME) clean: grm -rf $(DESTDIR) grm -rf $(PKGDIR) grm -f solaris/prototype grm -f $(PKGNAME)-$(SOLARIS_VERSION)-`uname -s``uname -r`-all-CSW.pkg mcollective-2.0.0/ext/solaris/prototype.head0000644000175000017500000000006111747546503020202 0ustar jonasjonasi pkginfo i postinstall i preremove i postremove mcollective-2.0.0/ext/solaris/postinstall0000644000175000017500000000050711747546503017616 0ustar jonasjonas#! /bin/bash PKG_INSTALL_ROOT=${PKG_INSTALL_ROOT:-/} /usr/bin/test -d $PKG_INSTALL_ROOT/etc/mcollective || /usr/sbin/chroot $PKG_INSTALL_ROOT /usr/bin/ln -s /etc/opt/csw/mcollective /etc/mcollective /usr/sbin/chroot $PKG_INSTALL_ROOT /usr/sbin/svccfg import /var/opt/csw/svc/manifest/network/cswmcollectived.xml || /bin/true mcollective-2.0.0/ext/solaris/postremove0000644000175000017500000000027411747546503017446 0ustar jonasjonas/usr/sbin/svcadm disable svc:network/cswmcollectived 2>/dev/null || /bin/true /usr/sbin/svccfg delete svc:network/cswmcollectived 2>/dev/null || /bin/true rm /etc/mcollective || /bin/true mcollective-2.0.0/ext/solaris/cswmcollectived.xml0000644000175000017500000000467011747546503021235 0ustar jonasjonas mcollective-2.0.0/ext/activemq/0000755000175000017500000000000011751200300015423 5ustar jonasjonasmcollective-2.0.0/ext/activemq/examples/0000755000175000017500000000000011747546503017270 5ustar jonasjonasmcollective-2.0.0/ext/activemq/examples/single-broker/0000755000175000017500000000000011747546503022033 5ustar jonasjonasmcollective-2.0.0/ext/activemq/examples/single-broker/README0000644000175000017500000000027311747546503022715 0ustar jonasjonasSimple single broker setup for ActiveMQ 5.5.0. Provides 2 users, one admin and one for mcollective. Admin user can create all sorts of queues and topics, mcollective user is restricted. mcollective-2.0.0/ext/activemq/examples/single-broker/activemq.xml0000644000175000017500000000626511747546503024377 0ustar jonasjonas file:${activemq.base}/conf/credentials.properties mcollective-2.0.0/ext/activemq/examples/multi-broker/0000755000175000017500000000000011747546503021704 5ustar jonasjonasmcollective-2.0.0/ext/activemq/examples/multi-broker/broker1-activemq.xml0000755000175000017500000001270611747546503025613 0ustar jonasjonas file:${activemq.base}/conf/credentials.properties mcollective-2.0.0/ext/activemq/examples/multi-broker/README0000644000175000017500000000053611747546503022570 0ustar jonasjonas3 ActiveMQ servers clustered together in a star foramt: broker2 <-----> broker1 <----> broker3 Pay attention to the names in the config file as well as the users. This is identical to the simple single broker example except with the aded amq user for the clustering and the connection setups in broker1 Tested to work with ActiveMQ 5.5.0 mcollective-2.0.0/ext/activemq/examples/multi-broker/broker2-activemq.xml0000755000175000017500000000642111747546503025611 0ustar jonasjonas file:${activemq.base}/conf/credentials.properties mcollective-2.0.0/ext/activemq/examples/multi-broker/broker3-activemq.xml0000755000175000017500000000642111747546503025612 0ustar jonasjonas file:${activemq.base}/conf/credentials.properties mcollective-2.0.0/ext/windows/0000755000175000017500000000000011747546503015333 5ustar jonasjonasmcollective-2.0.0/ext/windows/environment.bat0000644000175000017500000000055011747546503020367 0ustar jonasjonasSET BASEDIR=%~dp0.. SET BASEDIR=%BASEDIR:\bin\..=% SET SERVER_CONFIG=%BASEDIR%\etc\server.cfg SET CLIENT_CONFIG=%BASEDIR%\etc\client.cfg SET MCOLLECTIVED=%BASEDIR%\bin\mcollectived SET MC_STARTTYPE=manual REM SET MC_STARTTYPE=auto SET PATH=%BASEDIR%\bin;%PATH% SET RUBYLIB=%BASEDIR%\lib;%RUBYLIB% SET RUBYLIB=%RUBYLIB:\=/% SET RUBY="ruby" mcollective-2.0.0/ext/windows/mco.bat0000644000175000017500000000015611747546503016603 0ustar jonasjonas@echo off SETLOCAL call "%~dp0environment.bat" %0 %* %RUBY% -S -- mco %* --config "%CLIENT_CONFIG%" mcollective-2.0.0/ext/windows/README.md0000644000175000017500000000312411747546503016612 0ustar jonasjonasThese files support installing and using mcollective on MS Windows. Here are a few instructions for people who wish to do early adopter testing, before 2.0 is out we hope to have this packaged into a msi installer but your early feedback will help. Assuming you are installing mcollective into C:\marionette-collective: * Install Ruby from http://rubyinstaller.org/, use 1.8.7 * Install the following gems: stomp, win32-process, win32-service, sys-admin, windows-api * extract the zip file or clone the git repo into C:\marionette-collective * copy the files from C:\marionette-collective\ext\windows\*.* into C:\marionette-collective\bin * Install any plugins and their dependencies into C:\marionette-collective\plugins specifically for the package and service agents you can install Puppet via gems * Edit the configuration files setting: * libdir = c:\marionette-collective\plugins * logfile = c:\marionette-collective\mcollective.log * plugin.yaml = c:\marionette-collective\etc\facts.ysml * daemonize = 1 * change directories to c:\marionette-collective\bin and run register_service.bat At this point you would have your service registered into the windows service manager but set to manual start. If you start it there it should run ok. If it does not run: * Look in the log files, set it to debug level * If the log files are empty look at the command the service wrapper runs and run it by hand. This will show you any early exception preventing it from running. It wont succesfully start but you should see why it does not get far enough to start writing logs. mcollective-2.0.0/ext/windows/unregister_service.bat0000644000175000017500000000015311747546503021731 0ustar jonasjonas@echo off SETLOCAL call "%~dp0environment.bat" %0 %* %RUBY% -S -- service_manager.rb --uninstall mcollective-2.0.0/ext/windows/service_manager.rb0000644000175000017500000000452111747546503021014 0ustar jonasjonasrequire 'optparse' opt = OptionParser.new ruby_path = ENV["RUBY"].gsub('"','') basedir = ENV["BASEDIR"] libdir = ENV["RUBYLIB"] mcollectived = ENV["MCOLLECTIVED"] configfile = ENV["SERVER_CONFIG"] unless File.exist?(ruby_path) ENV["PATH"].split(File::PATH_SEPARATOR).each do |path| ruby = File.join(path, "ruby.exe") if File.exist?(ruby) ruby_path = ruby break end end end abort("Can't find ruby.ext in the path") unless ruby_path options = {:name => "mcollectived", :display_name => "The Marionette Collective", :description => "Puppet Labs server orchestration framework", :command => '%s -I"%s" -- "%s" --config "%s"' % [ ruby_path, libdir, mcollectived, configfile ]} action = false opt.on("--install", "Install service") do action = :install end opt.on("--uninstall", "Remove service") do action = :uninstall end opt.on("--name NAME", String, "Service name (#{options[:name]})") do |n| options[:name] = n end opt.on("--description DESCRIPTION", String, "Service description (#{options[:description]})") do |v| options[:description] = v end opt.on("--display NAME", String, "Service display name (#{options[:display_name]})") do |n| options[:display_name] = n end opt.on("--command COMMAND", String, "Service command (#{options[:command]})") do |c| options[:command] = c end opt.parse! abort "Please choose an action with --install or --uninstall" unless action require 'rubygems' require 'win32/service' include Win32 case action when :install if ENV["MC_STARTTYPE"] =~ /auto/i start_type = Service::AUTO_START else start_type = Service::DEMAND_START end Service.new( :service_name => options[:name], :display_name => options[:display_name], :description => options[:description], :binary_path_name => options[:command], :service_type => Service::SERVICE_WIN32_OWN_PROCESS, :start_type => start_type ) puts "Service %s installed" % [options[:name]] when :uninstall Service.stop(options[:name]) unless Service.status(options[:name]).current_state == 'stopped' while Service.status(options[:name]).current_state != 'stopped' puts "Waiting for service %s to stop" % [options[:name]] sleep 1 end Service.delete(options[:name]) puts "Service %s removed" % [options[:name]] end mcollective-2.0.0/ext/windows/register_service.bat0000644000175000017500000000015111747546503021364 0ustar jonasjonas@echo off SETLOCAL call "%~dp0environment.bat" %0 %* %RUBY% -S -- service_manager.rb --install mcollective-2.0.0/ext/help-templates/0000755000175000017500000000000011747546503016565 5ustar jonasjonasmcollective-2.0.0/ext/help-templates/README0000644000175000017500000000007611747546503017450 0ustar jonasjonasA number of templates for the SimpleRPC DDL based help system mcollective-2.0.0/ext/help-templates/rpc-help-markdown.erb0000644000175000017500000000302011747546503022604 0ustar jonasjonas<%= meta[:name].upcase %> AGENT <% (meta[:name].size + 7).times do %>=<% end %> <%= meta[:description] %> Author: <%= meta[:author] %> Version: <%= meta[:version] %> License: <%= meta[:license] %> Timeout: <%= meta[:timeout] %> Home Page: <%= meta[:url] %> ACTIONS: ======== % actions.keys.sort.each do |action| * <%= action %> % end % actions.keys.sort.each do |action| _<%= action %>_ action: <% (action.size + 8).times do %>-<% end %> <%= actions[action][:description] %> % if actions[action][:input].keys.size > 0 INPUT: % end % actions[action][:input].keys.sort.each do |input| <%= input %>: Description: <%= actions[action][:input][input][:description] %> Prompt: <%= actions[action][:input][input][:prompt] %> Type: <%= actions[action][:input][input][:type] %> % if actions[action][:input][input][:type] == :string Validation: <%= actions[action][:input][input][:validation] %> Length: <%= actions[action][:input][input][:maxlength] %> % elsif actions[action][:input][input][:type] == :list Valid Values: <%= actions[action][:input][input][:list].join(", ") %> % end % end OUTPUT: % actions[action][:output].keys.sort.each do |output| <%= output %>: Description: <%= actions[action][:output][output][:description] %> Display As: <%= actions[action][:output][output][:display_as] %> % end % end mcollective-2.0.0/ext/mc-irb0000755000175000017500000001460011747546503014741 0ustar jonasjonas#!/usr/bin/env ruby # Simple IRB shell for mcollective # # mc-irb nrpe # Determining the amount of hosts matching filter for 2 seconds .... 47 # >> rpc :runcommand, :command => "check_disks" # # * [ ============================================================> ] 47 / 47 # # # dev1.your.net Request Aborted # CRITICAL # Output: DISK CRITICAL - free space: / 176 MB (4% inode=86%); # Exit Code: 2 # Performance Data: /=3959MB;3706;3924;0;4361 /boot=26MB;83;88;0;98 /dev/shm=0MB;217;230;0;256 # # => true # >> mchelp # # => true # >> rpc(:runcommand, :command => "check_disks") do |resp| # ?> puts resp[:sender] + ": " + resp[:data][:output] # >> end # # * [ ============================================================> ] 47 / 47 # # dev1.your.net: DISK OK # # => true # >> # # You can access the agent variable via @agent from where you can do the usual manipulation of filters etc, # if you wish to switch to a different agent mid run just do newagent("some_other_agent") # # If you install the Bond gem you'll get some DDL assisted completion in the rpc method require 'rubygems' require 'irb' def consolize &block yield IRB.setup(nil) irb = IRB::Irb.new IRB.conf[:MAIN_CONTEXT] = irb.context irb.context.evaluate("require 'irb/completion'", 0) begin require 'bond' Bond.start Bond.complete(:method => "rpc") do |e| begin if e.argument == 1 if e.arguments.last == "?" puts "\n\nActions for #{@agent_name}:\n" @agent.ddl.actions.each do |action| puts "%20s - %s" % [ ":#{action}", @agent.ddl.action_interface(action)[:description] ] end print "\n" + e.line end @agent.ddl.actions elsif e.argument > 1 action = eval(e.arguments[0]).to_s ddl = @agent.ddl.action_interface(action) if e.arguments.last == "?" puts "\n\nArguments for #{action}:\n" ddl[:input].keys.each do |input| puts "%20s - %s" % [ ":#{input}", ddl[:input][input][:description] ] end print "\n" + e.line end [ddl[:input].keys, :verbose].flatten end rescue Exception [] end end rescue Exception end trap("SIGINT") do irb.signal_handle end catch(:IRB_EXIT) do irb.eval_input end end def mchelp system("mc-rpc --agent-help #{@agent_name}|less") true end def rpc(method_name, *args, &block) unless block_given? if args.size > 0 args = args.first else args = {} end if args[:verbose] args.delete(:verbose) printrpc(@agent.send(method_name, args), :verbose => true) printrpcstats else printrpc @agent.send(method_name, args) printrpcstats end else @agent.send(method_name, args.first).each do |resp| yield resp end printrpcstats end true rescue MCollective::DDLValidationError => e puts "Request did not pass DDL validation: #{e}" end def print_filter puts "Active Filter matched #{discover.size} hosts:" puts "\tIdentity: #{@agent.filter['identity'].pretty_inspect}" puts "\t Classes: #{@agent.filter['cf_class'].pretty_inspect}" puts "\t Facts: #{@agent.filter['fact'].pretty_inspect}" puts "\t Agents: #{@agent.filter['agent'].pretty_inspect}" discover.size > 0 ? true : false end def newagent(agent) @agent_name = agent @options[:filter]["agent"] = [] @agent = rpcclient(@agent_name, :options => @options) discover @agent.progress = true print_filter end def identity_filter(*args) @agent.identity_filter(*args) print_filter end def fact_filter(*args) @agent.fact_filter(*args) print_filter end def agent_filter(*args) @agent.agent_filter(*args) print_filter end def class_filter(*args) @agent.class_filter(*args) print_filter end def reset_filter @agent.reset_filter print_filter end def reset @agent.reset print_filter end def discover @agent.discover end def mc? puts < for a list of actions or arguments, do simple : to get completion on action names and arguments without description of each EOF true end consolize do require 'mcollective' include MCollective::RPC @options = rpcoptions unless ARGV.size == 1 puts "Please specify an agent name on the command line" exit 1 end puts "The Marionette Collective Interactive Ruby Shell version #{MCollective.version}" puts newagent(ARGV[0]) puts puts "Use mc? to get help on using this shell" end mcollective-2.0.0/ext/stompclient0000755000175000017500000001002211747546503016123 0ustar jonasjonas#!/usr/bin/env ruby # == Synopsis # # stompclient: Generic client to consume and produce STOMP queues and topics, tested against # Apache Active MQ # # == Description # A simple client that can connect to an STOMP server, subscribe to topics and queues and also # send to topics and queues. # # == Usage # stompclient [OPTIONS] # # --help, -h: # Show Help # # --server, -s # The server to connect to, can also be set in STOMP_SERVER environment variable # # --port, -p # The port to connect to, default to 6163 # # --user, -u # The user to connect as, can also be set in STOMP_USER environment variable # # --password, -P # The password to use, can also be set in STOMP_PASSWORD environment variable # # When connected to a server, use the 'help' command to see further information about # using the client, common commands that can be issued are: # # - subscribe /topic/foo: Subscribes to topic 'foo' # - /topic/foo bar: Sends 'bar' to the topic 'foo' # - details: Toggle the display or timestamp and topic or queue information for each message # # # == Changelog # - 20 December 2009 Include into MCollective # - 17 March 2009 Initial release # # R.I.Pienaar more information at www.devco.net # # Licensed under the Apache License, Version 2.0 require 'rubygems' require 'stomp' require 'readline' require 'thread' require 'getoptlong' opts = GetoptLong.new( [ '--server', '-s', GetoptLong::REQUIRED_ARGUMENT], [ '--port', '-p', GetoptLong::REQUIRED_ARGUMENT], [ '--user', '-u', GetoptLong::REQUIRED_ARGUMENT], [ '--password', '-P', GetoptLong::REQUIRED_ARGUMENT], [ '--help', '-h', GetoptLong::NO_ARGUMENT] ) @user = ENV["STOMP_USER"]; @password = ENV["STOMP_PASSWORD"] @server = ENV["STOMP_SERVER"] @port = ENV["STOMP_PORT"] || 6163 opts.each { |opt, arg| case opt when '--help' begin require 'rdoc/ri/ri_paths' require 'rdoc/usage' RDoc::usage exit rescue Exception => e puts("Install RDoc::usage or view the comments in the top of the script to get detailed help") if e.to_str != "exit" end exit when '--server' @server = arg when '--port' @port = arg when '--user' @user = arg when '--password' @password = arg end } @conn = Stomp::Connection.open(@user, @password, @server, @port, true) STDOUT.sync = true def showhelp puts("List of commands:") puts("\n\t- subscribe /(topic|queue)/foo subscribes to topic of queue 'foo'") puts("\t- /(topic|queue|/foo bar sends msg 'bar' to topic of queue 'foo'") puts("\t- quit|exit|q|^d exit") puts("\t- detail show/dont show time and topic a msg was received on") puts("\t- help show this help") end @showdetails = true Thread.new(@conn) do |amq| while true msg = amq.receive dest = msg.headers["destination"] time = Time.now.strftime('%H:%M:%S') if @showdetails msg = "\r#{time}:#{dest} > #{msg.body.chomp}\n" else msg = "\r#{msg.body.chomp}\n" end puts (msg) end end loop do line = Readline::readline('AMQ> ') if line Readline::HISTORY.push(line) if line != "" else exit end if (line =~ /^(\/(topic|queue)\/\S+)\s+(.+)$/) puts("Sending '#{$3}' to #{$1}") if @conn.respond_to?("publish") @conn.publish($1, $3) else @conn.send($1, $3) end elsif (line =~ /^sub\S* (\/(topic|queue)\/\S+)$/) puts("Subscribing to #{$1}") @conn.subscribe($1) elsif (line =~ /^det(ail)*$/) if @showdetails @showdetails = false puts("No longer showing details") else @showdetails = true puts("Showing time and topic for each msg") end elsif (line =~ /^(quit|exit|q)$/) exit elsif (line =~ /^(help|h|\?)$/) showhelp elsif (line =~ /^$/) else puts("ERROR: unrecognised input: #{line}") end end mcollective-2.0.0/ext/perl/0000755000175000017500000000000011747546503014603 5ustar jonasjonasmcollective-2.0.0/ext/perl/mc-find-hosts.pl0000644000175000017500000000450111747546503017613 0ustar jonasjonas#!/usr/bin/perl # A simple Perl client for mcollective that just demonstrates # how to construct requests, send them and process results. # # This is in effect a mc-find-hosts equivelant, you can fill in # filters in the request and only the matching ones will reply. # # For this to work you need the SSL security plugin in MCollective # 1.0.0 set to operate in YAML mode. use YAML::Syck; use Digest::MD5 qw(md5 md5_hex md5_base64); use Crypt::OpenSSL::RSA; use MIME::Base64; use Net::STOMP::Client; use Data::Dumper; # The topics from your activemq, /topic/mcollective_dev/... $mcollective_prefix = "mcollective_dev"; # Path to your SSL private key and what it's called so the # mcollectived will load yours $ssl_private_key = "/path/to/your.pem"; $ssl_private_key_name = "you"; # A string representing your sending host $mcollective_client_identity = "devel.your.com-perl"; # Stomp connection parameters $stomp_host = "localhost"; $stomp_port = 6163; $stomp_user = "your"; $stomp_password = "secret"; $YAML::Syck::ImplicitTyping = 1; $request{":msgtime"} = time(); $request{":filter"}{"identity"} = []; $request{":filter"}{"fact"} = []; $request{":filter"}{"agent"} = []; $request{":filter"}{"cf_class"} = []; $request{":requestid"} = md5_hex(time() . $$); $request{":callerid"} = "cert=${ssl_private_key_name}"; $request{":senderid"} = $mcollective_client_identity; $request{":body"} = Dump("ping"); $request{":msgtarget"} = "/topic/${mcollective_prefix}.discovery.command"; $key = ""; open(SSL, $ssl_private_key); while() { $key = $key . $_; } close(SSL); $rsa = Crypt::OpenSSL::RSA->new_private_key($key); $request{":hash"} = encode_base64($rsa->sign($request{":body"})); $mcrequest = Dump(\%request); $stomp = Net::STOMP::Client->new(host => $stomp_host, port => $stomp_port); $stomp->connect(login => $stomp_user, passcode => $stomp_password); $stomp->message_callback(sub { my ($self, $frame) = @_; $mc_reply = Load($frame->body); $mc_body = Load($mc_reply->{":body"}); print $mc_reply->{":senderid"} . "> " . $mc_body . "\n"; return($self); }); $stomp->subscribe(destination => "/topic/${mcollective_prefix}.discovery.reply"); $stomp->send(destination => "/topic/${mcollective_prefix}.discovery.command", body => $mcrequest); $stomp->wait_for_frames(callback => sub { return(0) }, timeout => 5); $stomp->disconnect(); mcollective-2.0.0/lib/0000755000175000017500000000000011747546503013607 5ustar jonasjonasmcollective-2.0.0/lib/mcollective/0000755000175000017500000000000011747546503016115 5ustar jonasjonasmcollective-2.0.0/lib/mcollective/util.rb0000644000175000017500000002076011747546503017424 0ustar jonasjonasmodule MCollective # Some basic utility helper methods useful to clients, agents, runner etc. module Util # Finds out if this MCollective has an agent by the name passed # # If the passed name starts with a / it's assumed to be regex # and will use regex to match def self.has_agent?(agent) agent = Regexp.new(agent.gsub("\/", "")) if agent.match("^/") if agent.is_a?(Regexp) if Agents.agentlist.grep(agent).size > 0 return true else return false end else return Agents.agentlist.include?(agent) end false end # On windows ^c can't interrupt the VM if its blocking on # IO, so this sets up a dummy thread that sleeps and this # will have the end result of being interruptable at least # once a second. This is a common pattern found in Rails etc def self.setup_windows_sleeper Thread.new { loop { sleep 1 } } if Util.windows? end # Checks if this node has a configuration management class by parsing the # a text file with just a list of classes, recipes, roles etc. This is # ala the classes.txt from puppet. # # If the passed name starts with a / it's assumed to be regex # and will use regex to match def self.has_cf_class?(klass) klass = Regexp.new(klass.gsub("\/", "")) if klass.match("^/") cfile = Config.instance.classesfile Log.debug("Looking for configuration management classes in #{cfile}") begin File.readlines(cfile).each do |k| if klass.is_a?(Regexp) return true if k.chomp.match(klass) else return true if k.chomp == klass end end rescue Exception => e Log.warn("Parsing classes file '#{cfile}' failed: #{e.class}: #{e}") end false end # Gets the value of a specific fact, mostly just a duplicate of MCollective::Facts.get_fact # but it kind of goes with the other classes here def self.get_fact(fact) Facts.get_fact(fact) end # Compares fact == value, # # If the passed value starts with a / it's assumed to be regex # and will use regex to match def self.has_fact?(fact, value, operator) Log.debug("Comparing #{fact} #{operator} #{value}") Log.debug("where :fact = '#{fact}', :operator = '#{operator}', :value = '#{value}'") fact = Facts[fact] return false if fact.nil? fact = fact.clone if operator == '=~' # to maintain backward compat we send the value # as /.../ which is what 1.0.x needed. this strips # off the /'s wich is what we need here if value =~ /^\/(.+)\/$/ value = $1 end return true if fact.match(Regexp.new(value)) elsif operator == "==" return true if fact == value elsif ['<=', '>=', '<', '>', '!='].include?(operator) # Yuk - need to type cast, but to_i and to_f are overzealous if value =~ /^[0-9]+$/ && fact =~ /^[0-9]+$/ fact = Integer(fact) value = Integer(value) elsif value =~ /^[0-9]+.[0-9]+$/ && fact =~ /^[0-9]+.[0-9]+$/ fact = Float(fact) value = Float(value) end return true if eval("fact #{operator} value") end false end # Checks if the configured identity matches the one supplied # # If the passed name starts with a / it's assumed to be regex # and will use regex to match def self.has_identity?(identity) identity = Regexp.new(identity.gsub("\/", "")) if identity.match("^/") if identity.is_a?(Regexp) return Config.instance.identity.match(identity) else return true if Config.instance.identity == identity end false end # Checks if the passed in filter is an empty one def self.empty_filter?(filter) filter == empty_filter || filter == {} end # Creates an empty filter def self.empty_filter {"fact" => [], "cf_class" => [], "agent" => [], "identity" => [], "compound" => []} end # Picks a config file defaults to ~/.mcollective # else /etc/mcollective/client.cfg def self.config_file_for_user # expand_path is pretty lame, it relies on HOME environment # which isnt't always there so just handling all exceptions # here as cant find reverting to default begin config = File.expand_path("~/.mcollective") unless File.readable?(config) && File.file?(config) config = "/etc/mcollective/client.cfg" end rescue Exception => e config = "/etc/mcollective/client.cfg" end return config end # Creates a standard options hash def self.default_options {:verbose => false, :disctimeout => 2, :timeout => 5, :config => config_file_for_user, :collective => nil, :filter => empty_filter} end def self.make_subscriptions(agent, type, collective=nil) config = Config.instance raise("Unknown target type #{type}") unless [:broadcast, :directed, :reply].include?(type) if collective.nil? config.collectives.map do |c| {:agent => agent, :type => type, :collective => c} end else raise("Unknown collective '#{collective}' known collectives are '#{config.collectives.join ', '}'") unless config.collectives.include?(collective) [{:agent => agent, :type => type, :collective => collective}] end end # Helper to subscribe to a topic on multiple collectives or just one def self.subscribe(targets) connection = PluginManager["connector_plugin"] targets = [targets].flatten targets.each do |target| connection.subscribe(target[:agent], target[:type], target[:collective]) end end # Helper to unsubscribe to a topic on multiple collectives or just one def self.unsubscribe(targets) connection = PluginManager["connector_plugin"] targets = [targets].flatten targets.each do |target| connection.unsubscribe(target[:agent], target[:type], target[:collective]) end end # Wrapper around PluginManager.loadclass def self.loadclass(klass) PluginManager.loadclass(klass) end # Parse a fact filter string like foo=bar into the tuple hash thats needed def self.parse_fact_string(fact) if fact =~ /^([^ ]+?)[ ]*=>[ ]*(.+)/ return {:fact => $1, :value => $2, :operator => '>=' } elsif fact =~ /^([^ ]+?)[ ]*=<[ ]*(.+)/ return {:fact => $1, :value => $2, :operator => '<=' } elsif fact =~ /^([^ ]+?)[ ]*(<=|>=|<|>|!=|==|=~)[ ]*(.+)/ return {:fact => $1, :value => $3, :operator => $2 } elsif fact =~ /^(.+?)[ ]*=[ ]*\/(.+)\/$/ return {:fact => $1, :value => "/#{$2}/", :operator => '=~' } elsif fact =~ /^([^= ]+?)[ ]*=[ ]*(.+)/ return {:fact => $1, :value => $2, :operator => '==' } else raise "Could not parse fact #{fact} it does not appear to be in a valid format" end end # Escapes a string so it's safe to use in system() or backticks # # Taken from Shellwords#shellescape since it's only in a few ruby versions def self.shellescape(str) return "''" if str.empty? str = str.dup # Process as a single byte sequence because not all shell # implementations are multibyte aware. str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1") # A LF cannot be escaped with a backslash because a backslash + LF # combo is regarded as line continuation and simply ignored. str.gsub!(/\n/, "'\n'") return str end def self.windows? !!(RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i) end def self.eval_compound_statement(expression) if expression.values.first =~ /^\// return Util.has_cf_class?(expression.values.first) elsif expression.values.first =~ />=|<=|=|<|>/ optype = expression.values.first.match(/>=|<=|=|<|>/) name, value = expression.values.first.split(optype[0]) unless value.split("")[0] == "/" optype[0] == "=" ? optype = "==" : optype = optype[0] else optype = "=~" end return Util.has_fact?(name,value, optype).to_s else return Util.has_cf_class?(expression.values.first) end end # Returns the current ruby version as per RUBY_VERSION, mostly # doing this here to aid testing def self.ruby_version RUBY_VERSION end end end mcollective-2.0.0/lib/mcollective/monkey_patches.rb0000644000175000017500000000473211747546503021461 0ustar jonasjonas# Make arrays of Symbols sortable class Symbol include Comparable def <=>(other) self.to_s <=> other.to_s end unless method_defined?("<=>") end # This provides an alias for RbConfig to Config for versions of Ruby older then # # version 1.8.5. This allows us to use RbConfig in place of the older Config in # # our code and still be compatible with at least Ruby 1.8.1. # require 'rbconfig' unless defined? ::RbConfig ::RbConfig = ::Config end # a method # that walks an array in groups, pass a block to # call the block on each sub array class Array def in_groups_of(chunk_size, padded_with=nil, &block) arr = self.clone # how many to add padding = chunk_size - (arr.size % chunk_size) # pad at the end arr.concat([padded_with] * padding) unless padding == chunk_size # how many chunks we'll make count = arr.size / chunk_size # make that many arrays result = [] count.times {|s| result << arr[s * chunk_size, chunk_size]} if block_given? result.each_with_index do |a, i| case block.arity when 1 yield(a) when 2 yield(a, (i == result.size - 1)) else raise "Expected 1 or 2 arguments, got #{block.arity}" end end else result end end unless method_defined?(:in_groups_of) end class Dir def self.mktmpdir(prefix_suffix=nil, tmpdir=nil) case prefix_suffix when nil prefix = "d" suffix = "" when String prefix = prefix_suffix suffix = "" when Array prefix = prefix_suffix[0] suffix = prefix_suffix[1] else raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}" end tmpdir ||= Dir.tmpdir t = Time.now.strftime("%Y%m%d") n = nil begin path = "#{tmpdir}/#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}" path << "-#{n}" if n path << suffix Dir.mkdir(path, 0700) rescue Errno::EEXIST n ||= 0 n += 1 retry end if block_given? begin yield path ensure FileUtils.remove_entry_secure path end else path end end unless method_defined?(:mktmpdir) def self.tmpdir tmp = '.' for dir in [ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], '/tmp'] if dir and stat = File.stat(dir) and stat.directory? and stat.writable? tmp = dir break end rescue nil end File.expand_path(tmp) end unless method_defined?(:tmpdir) end mcollective-2.0.0/lib/mcollective/config.rb0000644000175000017500000001642411747546503017716 0ustar jonasjonasmodule MCollective # A pretty sucky config class, ripe for refactoring/improving class Config include Singleton attr_reader :topicprefix, :daemonize, :pluginconf, :libdir, :configured, :logfile, :keeplogs, :max_log_size, :loglevel, :logfacility, :identity, :daemonize, :connector, :securityprovider, :factsource, :registration, :registerinterval, :topicsep, :classesfile, :rpcauditprovider, :rpcaudit, :configdir, :rpcauthprovider, :rpcauthorization, :color, :configfile, :rpchelptemplate, :rpclimitmethod, :logger_type, :fact_cache_time, :collectives, :main_collective, :ssl_cipher, :registration_collective, :direct_addressing, :direct_addressing_threshold, :queueprefix, :ttl def initialize @configured = false end def loadconfig(configfile) set_config_defaults(configfile) if File.exists?(configfile) File.open(configfile, "r").each do |line| # strip blank spaces, tabs etc off the end of all lines line.gsub!(/\s*$/, "") unless line =~ /^#|^$/ if (line =~ /(.+?)\s*=\s*(.+)/) key = $1 val = $2 case key when "topicsep" @topicsep = val when "registration" @registration = val.capitalize when "registration_collective" @registration_collective = val when "registerinterval" @registerinterval = val.to_i when "collectives" @collectives = val.split(",").map {|c| c.strip} when "main_collective" @main_collective = val when "topicprefix" @topicprefix = val when "queueprefix" @queueprefix = val when "logfile" @logfile = val when "keeplogs" @keeplogs = val.to_i when "max_log_size" @max_log_size = val.to_i when "loglevel" @loglevel = val when "logfacility" @logfacility = val when "libdir" paths = val.split(File::PATH_SEPARATOR) paths.each do |path| @libdir << path unless $LOAD_PATH.include?(path) $LOAD_PATH << path end end when "identity" @identity = val when "direct_addressing" val =~ /^1|y/i ? @direct_addressing = true : @direct_addressing = false when "direct_addressing_threshold" @direct_addressing_threshold = val.to_i when "color" val =~ /^1|y/i ? @color = true : @color = false when "daemonize" val =~ /^1|y/i ? @daemonize = true : @daemonize = false when "securityprovider" @securityprovider = val.capitalize when "factsource" @factsource = val.capitalize when "connector" @connector = val.capitalize when "classesfile" @classesfile = val when /^plugin.(.+)$/ @pluginconf[$1] = val when "rpcaudit" val =~ /^1|y/i ? @rpcaudit = true : @rpcaudit = false when "rpcauditprovider" @rpcauditprovider = val.capitalize when "rpcauthorization" val =~ /^1|y/i ? @rpcauthorization = true : @rpcauthorization = false when "rpcauthprovider" @rpcauthprovider = val.capitalize when "rpchelptemplate" @rpchelptemplate = val when "rpclimitmethod" @rpclimitmethod = val.to_sym when "logger_type" @logger_type = val when "fact_cache_time" @fact_cache_time = val.to_i when "ssl_cipher" @ssl_cipher = val when "ttl" @ttl = val.to_i else raise("Unknown config parameter #{key}") end end end end read_plugin_config_dir("#{@configdir}/plugin.d") raise 'Identities can only match /\w\.\-/' unless @identity.match(/^[\w\.\-]+$/) @configured = true @libdir.each {|dir| Log.warn("Cannot find libdir: #{dir}") unless File.directory?(dir)} if @logger_type == "syslog" raise "The sylog logger is not usable on the Windows platform" if Util.windows? end PluginManager.loadclass("Mcollective::Facts::#{@factsource}_facts") PluginManager.loadclass("Mcollective::Connector::#{@connector}") PluginManager.loadclass("Mcollective::Security::#{@securityprovider}") PluginManager.loadclass("Mcollective::Registration::#{@registration}") PluginManager.loadclass("Mcollective::Audit::#{@rpcauditprovider}") if @rpcaudit PluginManager << {:type => "global_stats", :class => RunnerStats.new} else raise("Cannot find config file '#{configfile}'") end end def set_config_defaults(configfile) @stomp = Hash.new @subscribe = Array.new @pluginconf = Hash.new @connector = "Stomp" @securityprovider = "Psk" @factsource = "Yaml" @identity = Socket.gethostname @registration = "Agentlist" @registerinterval = 0 @registration_collective = nil @topicsep = "." @topicprefix = "/topic/" @queueprefix = "/queue/" @classesfile = "/var/lib/puppet/state/classes.txt" @rpcaudit = false @rpcauditprovider = "" @rpcauthorization = false @rpcauthprovider = "" @configdir = File.dirname(configfile) @color = !Util.windows? @configfile = configfile @logger_type = "file" @keeplogs = 5 @max_log_size = 2097152 @rpclimitmethod = :first @libdir = Array.new @fact_cache_time = 300 @loglevel = "info" @logfacility = "user" @collectives = ["mcollective"] @main_collective = @collectives.first @ssl_cipher = "aes-256-cbc" @direct_addressing = false @direct_addressing_threshold = 10 @ttl = 60 # look in the config dir for the template so users can provide their own and windows # with odd paths will just work more often, but fall back to old behavior if it does # not exist @rpchelptemplate = File.join(File.dirname(configfile), "rpc-help.erb") @rpchelptemplate = "/etc/mcollective/rpc-help.erb" unless File.exists?(@rpchelptemplate) end def read_plugin_config_dir(dir) return unless File.directory?(dir) Dir.new(dir).each do |pluginconfigfile| next unless pluginconfigfile =~ /^([\w]+).cfg$/ plugin = $1 File.open("#{dir}/#{pluginconfigfile}", "r").each do |line| # strip blank lines line.gsub!(/\s*$/, "") next if line =~ /^#|^$/ if (line =~ /(.+?)\s*=\s*(.+)/) key = $1 val = $2 @pluginconf["#{plugin}.#{key}"] = val end end end end end end mcollective-2.0.0/lib/mcollective/runnerstats.rb0000644000175000017500000000400411747546503021030 0ustar jonasjonasmodule MCollective # Class to store stats about the mcollectived, it should live in the PluginManager # so that agents etc can get hold of it and return the stats to callers class RunnerStats def initialize @starttime = Time.now.to_i @validated = 0 @unvalidated = 0 @filtered = 0 @passed = 0 @total = 0 @replies = 0 @ttlexpired = 0 @mutex = Mutex.new end # Records a message that failed TTL checks def ttlexpired Log.debug("Incrementing ttl expired stat") @ttlexpired += 1 end # Records a message that passed the filters def passed Log.debug("Incrementing passed stat") @passed += 1 end # Records a message that didnt pass the filters def filtered Log.debug("Incrementing filtered stat") @filtered += 1 end # Records a message that validated ok def validated Log.debug("Incrementing validated stat") @validated += 1 end def unvalidated Log.debug("Incrementing unvalidated stat") @unvalidated += 1 end # Records receipt of a message def received Log.debug("Incrementing total stat") @total += 1 end # Records sending a message def sent @mutex.synchronize do Log.debug("Incrementing replies stat") @replies += 1 end end # Returns a hash with all stats def to_hash stats = {:validated => @validated, :unvalidated => @unvalidated, :passed => @passed, :filtered => @filtered, :starttime => @starttime, :total => @total, :ttlexpired => @ttlexpired, :replies => @replies} reply = {:stats => stats, :threads => [], :pid => Process.pid, :times => {} } ::Process.times.each_pair{|k,v| k = k.to_sym reply[:times][k] = v } Thread.list.each do |t| reply[:threads] << "#{t.inspect}" end reply[:agents] = Agents.agentlist reply end end end mcollective-2.0.0/lib/mcollective/optionparser.rb0000644000175000017500000001230611747546503021171 0ustar jonasjonasmodule MCollective # A simple helper to build cli tools that supports a uniform command line # layout. class Optionparser attr_reader :parser # Creates a new instance of the parser, you can supply defaults and include named groups of options. # # Starts a parser that defaults to verbose and that includs the filter options: # # oparser = MCollective::Optionparser.new({:verbose => true}, "filter") # # Stats a parser in non verbose mode that does support discovery # # oparser = MCollective::Optionparser.new() # # Starts a parser in verbose mode that does not show the common options: # # oparser = MCollective::Optionparser.new({:verbose => true}, "filter", "common") def initialize(defaults = {}, include_sections = nil, exclude_sections = nil) @parser = ::OptionParser.new @include = [include_sections].flatten @exclude = [exclude_sections].flatten @options = Util.default_options @options.merge!(defaults) end # Parse the options returning the options, you can pass a block that adds additional options # to the Optionparser. # # The sample below starts a parser that also prompts for --arguments in addition to the defaults. # It also sets the description and shows a usage message specific to this app. # # options = oparser.parse{|parser, options| # parser.define_head "Control the mcollective controller daemon" # parser.banner = "Usage: sh-mcollective [options] command" # # parser.on('--arg', '--argument ARGUMENT', 'Argument to pass to agent') do |v| # options[:argument] = v # end # } # # Users can set default options that get parsed in using the MCOLLECTIVE_EXTRA_OPTS environemnt # variable def parse(&block) yield(@parser, @options) if block_given? add_required_options add_common_options unless @exclude.include?("common") @include.each do |i| next if @exclude.include?(i) options_name = "add_#{i}_options" send(options_name) if respond_to?(options_name) end @parser.environment("MCOLLECTIVE_EXTRA_OPTS") @parser.parse! @options[:collective] = Config.instance.main_collective unless @options[:collective] @options end # These options will be added if you pass 'filter' into the include list of the # constructor. def add_filter_options @parser.separator "" @parser.separator "Host Filters" @parser.on('-W', '--with FILTER', 'Combined classes and facts filter') do |f| f.split(" ").each do |filter| begin fact_parsed = parse_fact(filter) @options[:filter]["fact"] << fact_parsed rescue @options[:filter]["cf_class"] << filter end end end @parser.on('-S', '--select FILTER', 'Compound filter combining facts and classes') do |f| @options[:filter]["compound"] << MCollective::Matcher::Parser.new(f).execution_stack end @parser.on('-F', '--wf', '--with-fact fact=val', 'Match hosts with a certain fact') do |f| fact_parsed = parse_fact(f) @options[:filter]["fact"] << fact_parsed if fact_parsed end @parser.on('-C', '--wc', '--with-class CLASS', 'Match hosts with a certain config management class') do |f| @options[:filter]["cf_class"] << f end @parser.on('-A', '--wa', '--with-agent AGENT', 'Match hosts with a certain agent') do |a| @options[:filter]["agent"] << a end @parser.on('-I', '--wi', '--with-identity IDENT', 'Match hosts with a certain configured identity') do |a| @options[:filter]["identity"] << a end end # These options should always be present def add_required_options @parser.on('-c', '--config FILE', 'Load configuratuion from file rather than default') do |f| @options[:config] = f end @parser.on('-v', '--verbose', 'Be verbose') do |v| @options[:verbose] = v end @parser.on('-h', '--help', 'Display this screen') do puts @parser exit! 1 end end # These options will be added to most cli tools def add_common_options @parser.separator "" @parser.separator "Common Options" @parser.on('-T', '--target COLLECTIVE', 'Target messages to a specific sub collective') do |f| @options[:collective] = f end @parser.on('--dt', '--discovery-timeout SECONDS', Integer, 'Timeout for doing discovery') do |t| @options[:disctimeout] = t end @parser.on('-t', '--timeout SECONDS', Integer, 'Timeout for calling remote agents') do |t| @options[:timeout] = t end @parser.on('-q', '--quiet', 'Do not be verbose') do |v| @options[:verbose] = false end @parser.on('--ttl TTL', 'Set the message validity period') do |v| @options[:ttl] = v.to_i end @parser.on('--reply-to TARGET', 'Set a custom target for replies') do |v| @options[:reply_to] = v end end private # Parse a fact filter string like foo=bar into the tuple hash thats needed def parse_fact(fact) Util.parse_fact_string(fact) end end end mcollective-2.0.0/lib/mcollective/runner.rb0000644000175000017500000000701211747546503017753 0ustar jonasjonasmodule MCollective # The main runner for the daemon, supports running in the foreground # and the background, keeps detailed stats and provides hooks to access # all this information class Runner def initialize(configfile) @config = Config.instance @config.loadconfig(configfile) unless @config.configured @stats = PluginManager["global_stats"] @security = PluginManager["security_plugin"] @security.initiated_by = :node @connection = PluginManager["connector_plugin"] @connection.connect @agents = Agents.new unless Util.windows? Signal.trap("USR1") do Log.info("Reloading all agents after receiving USR1 signal") @agents.loadagents end Signal.trap("USR2") do Log.info("Cycling logging level due to USR2 signal") Log.cycle_level end else Util.setup_windows_sleeper end end # Starts the main loop, before calling this you should initialize the MCollective::Config singleton. def run Util.subscribe(Util.make_subscriptions("mcollective", :broadcast)) Util.subscribe(Util.make_subscriptions("mcollective", :directed)) if @config.direct_addressing # Start the registration plugin if interval isn't 0 begin PluginManager["registration_plugin"].run(@connection) unless @config.registerinterval == 0 rescue Exception => e Log.error("Failed to start registration plugin: #{e}") end loop do begin request = receive if request.agent == "mcollective" controlmsg(request) else agentmsg(request) end rescue SignalException => e Log.warn("Exiting after signal: #{e}") @connection.disconnect raise rescue MsgTTLExpired => e Log.warn(e) rescue NotTargettedAtUs => e Log.debug("Message does not pass filters, ignoring") rescue Exception => e Log.warn("Failed to handle message: #{e} - #{e.class}\n") Log.warn(e.backtrace.join("\n\t")) end end end private # Deals with messages directed to agents def agentmsg(request) Log.debug("Handling message for agent '#{request.agent}' on collective '#{request.collective}'") @agents.dispatch(request, @connection) do |reply_message| reply(reply_message, request) if reply_message end end # Deals with messages sent to our control topic def controlmsg(request) Log.debug("Handling message for mcollectived controller") begin case request.payload[:body] when /^stats$/ reply(@stats.to_hash, request) when /^reload_agent (.+)$/ reply("reloaded #{$1} agent", request) if @agents.loadagent($1) when /^reload_agents$/ reply("reloaded all agents", request) if @agents.loadagents else Log.error("Received an unknown message to the controller") end rescue Exception => e Log.error("Failed to handle control message: #{e}") end end # Receive a message from the connection handler def receive request = @connection.receive request.type = :request @stats.received request.decode! request.validate request end # Sends a reply to a specific target topic def reply(msg, request) msg = Message.new(msg, nil, :request => request) msg.encode! msg.publish @stats.sent end end end mcollective-2.0.0/lib/mcollective/unix_daemon.rb0000644000175000017500000000146311747546503020754 0ustar jonasjonasmodule MCollective class UnixDaemon # Daemonize the current process def self.daemonize fork do Process.setsid exit if fork Dir.chdir('/tmp') STDIN.reopen('/dev/null') STDOUT.reopen('/dev/null', 'a') STDERR.reopen('/dev/null', 'a') yield end end def self.daemonize_runner(pid=nil) raise "The Unix Daemonizer can not be used on the Windows Platform" if Util.windows? UnixDaemon.daemonize do if pid begin File.open(pid, 'w') {|f| f.write(Process.pid) } rescue Exception => e end end begin runner = Runner.new(nil) runner.run ensure File.unlink(pid) if pid && File.exist?(pid) end end end end end mcollective-2.0.0/lib/mcollective/pluginmanager.rb0000644000175000017500000001335611747546503021303 0ustar jonasjonasmodule MCollective # A simple plugin manager, it stores one plugin each of a specific type # the idea is that we can only have one security provider, one connector etc. module PluginManager @plugins = {} # Adds a plugin to the list of plugins, we expect a hash like: # # {:type => "base", # :class => foo.new} # # or like: # {:type => "base", # :class => "Foo::Bar"} # # In the event that we already have a class with the given type # an exception will be raised. # # If the :class passed is a String then we will delay instantiation # till the first time someone asks for the plugin, this is because most likely # the registration gets done by inherited() hooks, at which point the plugin class is not final. # # If we were to do a .new here the Class initialize method would get called and not # the plugins, we there for only initialize the classes when they get requested via [] # # By default all plugin instances are cached and returned later so there's # always a single instance. You can pass :single_instance => false when # calling this to instruct it to always return a new instance when a copy # is requested. This only works with sending a String for :class. def self.<<(plugin) plugin[:single_instance] = true unless plugin.include?(:single_instance) type = plugin[:type] klass = plugin[:class] single = plugin[:single_instance] raise("Plugin #{type} already loaded") if @plugins.include?(type) # If we get a string then store 'nil' as the instance, signalling that we'll # create the class later on demand. if klass.is_a?(String) @plugins[type] = {:loadtime => Time.now, :class => klass, :instance => nil, :single => single} Log.debug("Registering plugin #{type} with class #{klass} single_instance: #{single}") else @plugins[type] = {:loadtime => Time.now, :class => klass.class, :instance => klass, :single => true} Log.debug("Registering plugin #{type} with class #{klass.class} single_instance: true") end end # Removes a plugim the list def self.delete(plugin) @plugins.delete(plugin) if @plugins.include?(plugin) end # Finds out if we have a plugin with the given name def self.include?(plugin) @plugins.include?(plugin) end # Provides a list of plugins we know about def self.pluginlist @plugins.keys end # deletes all registered plugins def self.clear @plugins.clear end # Gets a plugin by type def self.[](plugin) raise("No plugin #{plugin} defined") unless @plugins.include?(plugin) klass = @plugins[plugin][:class] if @plugins[plugin][:single] # Create an instance of the class if one hasn't been done before if @plugins[plugin][:instance] == nil Log.debug("Returning new plugin #{plugin} with class #{klass}") @plugins[plugin][:instance] = create_instance(klass) else Log.debug("Returning cached plugin #{plugin} with class #{klass}") end @plugins[plugin][:instance] else Log.debug("Returning new plugin #{plugin} with class #{klass}") create_instance(klass) end end # use eval to create an instance of a class def self.create_instance(klass) begin eval("#{klass}.new") rescue Exception => e raise("Could not create instance of plugin #{klass}: #{e}") end end # Finds plugins in all configured libdirs # # find("agent") # # will return an array of just agent names, for example: # # ["puppetd", "package"] # # Can also be used to find files of other extensions: # # find("agent", "ddl") # # Will return the same list but only of files with extension .ddl # in the agent subdirectory def self.find(type, extension="rb") extension = ".#{extension}" unless extension.match(/^\./) plugins = [] Config.instance.libdir.each do |libdir| plugdir = File.join([libdir, "mcollective", type]) next unless File.directory?(plugdir) Dir.new(plugdir).grep(/#{extension}$/).map do |plugin| plugins << File.basename(plugin, extension) end end plugins.sort.uniq end # Finds and loads from disk all plugins from all libdirs that match # certain criteria. # # find_and_load("pluginpackager") # # Will find all .rb files in the libdir/mcollective/pluginpackager/ # directory in all libdirs and load them from disk. # # You can influence what plugins get loaded using a block notation: # # find_and_load("pluginpackager") do |plugin| # plugin.match(/puppet/) # end # # This will load only plugins matching /puppet/ def self.find_and_load(type, extension="rb") extension = ".#{extension}" unless extension.match(/^\./) klasses = find(type, extension).map do |plugin| if block_given? next unless yield(plugin) end "%s::%s::%s" % [ "MCollective", type.capitalize, plugin.capitalize ] end.compact klasses.sort.uniq.each {|klass| loadclass(klass, true)} end # Loads a class from file by doing some simple search/replace # on class names and then doing a require. def self.loadclass(klass, squash_failures=false) fname = klass.gsub("::", "/").downcase + ".rb" Log.debug("Loading #{klass} from #{fname}") load fname rescue Exception => e Log.error("Failed to load #{klass}: #{e}") raise unless squash_failures end # Grep's over the plugin list and returns the list found def self.grep(regex) @plugins.keys.grep regex end end end mcollective-2.0.0/lib/mcollective/registration.rb0000644000175000017500000000126111747546503021154 0ustar jonasjonasmodule MCollective # Registration is implimented using a module structure and installations can # configure which module they want to use. # # We provide a simple one that just sends back the list of current known agents # in MCollective::Registration::Agentlist, you can create your own: # # Create a module in plugins/mcollective/registration/.rb # # You can inherit from MCollective::Registration::Base in which case you just need # to supply a _body_ method, whatever this method returns will be send to the # middleware connection for an agent called _registration_ module Registration autoload :Base, "mcollective/registration/base" end end mcollective-2.0.0/lib/mcollective/security.rb0000644000175000017500000000174511747546503020320 0ustar jonasjonasmodule MCollective # Security is implimented using a module structure and installations # can configure which module they want to use. # # Security modules deal with various aspects of authentication and authorization: # # - Determines if a filter excludes this host from dealing with a request # - Serialization and Deserialization of messages # - Validation of messages against keys, certificates or whatever the class choose to impliment # - Encoding and Decoding of messages # # To impliment a new security class using SSL for example you would inherit from the base # class and only impliment: # # - decodemsg # - encodereply # - encoderequest # - validrequest? # # Each of these methods should increment various stats counters, see the default MCollective::Security::Psk module for examples of this # # Filtering can be extended by providing a new validate_filter? method. module Security autoload :Base, "mcollective/security/base" end end mcollective-2.0.0/lib/mcollective/security/0000755000175000017500000000000011747546503017764 5ustar jonasjonasmcollective-2.0.0/lib/mcollective/security/base.rb0000644000175000017500000002112511747546503021224 0ustar jonasjonasmodule MCollective module Security # This is a base class the other security modules should inherit from # it handles statistics and validation of messages that should in most # cases apply to all security models. # # To create your own security plugin you should provide a plugin that inherits # from this and provides the following methods: # # decodemsg - Decodes a message that was received from the middleware # encodereply - Encodes a reply message to a previous request message # encoderequest - Encodes a new request message # validrequest? - Validates a request received from the middleware # # Optionally if you are identifying users by some other means like certificate name # you can provide your own callerid method that can provide the rest of the system # with an id, and you would see this id being usable in SimpleRPC authorization methods # # The @initiated_by variable will be set to either :client or :node depending on # who is using this plugin. This is to help security providers that operate in an # asymetric mode like public/private key based systems. # # Specifics of each of these are a bit fluid and the interfaces for this is not # set in stone yet, specifically the encode methods will be provided with a helper # that takes care of encoding the core requirements. The best place to see how security # works is by looking at the provided MCollective::Security::PSK plugin. class Base attr_reader :stats attr_accessor :initiated_by # Register plugins that inherits base def self.inherited(klass) PluginManager << {:type => "security_plugin", :class => klass.to_s} end # Initializes configuration and logging as well as prepare a zero'd hash of stats # various security methods and filter validators should increment stats, see MCollective::Security::Psk for a sample def initialize @config = Config.instance @log = Log @stats = PluginManager["global_stats"] end # Takes a Hash with a filter in it and validates it against host information. # # At present this supports filter matches against the following criteria: # # - puppet_class|cf_class - Presence of a configuration management class in # the file configured with classesfile # - agent - Presence of a MCollective agent with a supplied name # - fact - The value of a fact avout this system # - identity - the configured identity of the system # # TODO: Support REGEX and/or multiple filter keys to be AND'd def validate_filter?(filter) failed = 0 passed = 0 passed = 1 if Util.empty_filter?(filter) filter.keys.each do |key| case key when /puppet_class|cf_class/ filter[key].each do |f| Log.debug("Checking for class #{f}") if Util.has_cf_class?(f) then Log.debug("Passing based on configuration management class #{f}") passed += 1 else Log.debug("Failing based on configuration management class #{f}") failed += 1 end end when "compound" filter[key].each do |compound| result = [] compound.each do |expression| case expression.keys.first when "statement" result << Util.eval_compound_statement(expression).to_s when "and" result << "&&" when "or" result << "||" when "(" result << "(" when ")" result << ")" when "not" result << "!" end end result = eval(result.join(" ")) if result Log.debug("Passing based on class and fact composition") passed +=1 else Log.debug("Failing based on class and fact composition") failed +=1 end end when "agent" filter[key].each do |f| if Util.has_agent?(f) || f == "mcollective" Log.debug("Passing based on agent #{f}") passed += 1 else Log.debug("Failing based on agent #{f}") failed += 1 end end when "fact" filter[key].each do |f| if Util.has_fact?(f[:fact], f[:value], f[:operator]) Log.debug("Passing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}") passed += 1 else Log.debug("Failing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}") failed += 1 end end when "identity" unless filter[key].empty? # Identity filters should not be 'and' but 'or' as each node can only have one identity matched = filter[key].select{|f| Util.has_identity?(f)}.size if matched == 1 Log.debug("Passing based on identity") passed += 1 else Log.debug("Failed based on identity") failed += 1 end end end end if failed == 0 && passed > 0 Log.debug("Message passed the filter checks") @stats.passed return true else Log.debug("Message failed the filter checks") @stats.filtered return false end end def create_reply(reqid, agent, body) Log.debug("Encoded a message for request #{reqid}") {:senderid => @config.identity, :requestid => reqid, :senderagent => agent, :msgtime => Time.now.utc.to_i, :body => body} end def create_request(reqid, filter, msg, initiated_by, target_agent, target_collective, ttl=60) Log.debug("Encoding a request for agent '#{target_agent}' in collective #{target_collective} with request id #{reqid}") {:body => msg, :senderid => @config.identity, :requestid => reqid, :filter => filter, :collective => target_collective, :agent => target_agent, :callerid => callerid, :ttl => ttl, :msgtime => Time.now.utc.to_i} end # Give a MC::Message instance and a message id this will figure out if you the incoming # message id matches the one the Message object is expecting and raise if its not # # Mostly used by security plugins to figure out if they should do the hard work of decrypting # etc messages that would only later on be ignored def should_process_msg?(msg, msgid) if msg.expected_msgid unless msg.expected_msgid == msgid msgtext = "Got a message with id %s but was expecting %s, ignoring message" % [msgid, msg.expected_msgid] Log.debug msgtext raise MsgDoesNotMatchRequestID, msgtext end end true end # Validates a callerid. We do not want to allow things like \ and / in # callerids since other plugins make assumptions that these are safe strings. # # callerids are generally in the form uid=123 or cert=foo etc so we do that # here but security plugins could override this for some complex uses def valid_callerid?(id) !!id.match(/^[\w]+=[\w\.\-]+$/) end # Returns a unique id for the caller, by default we just use the unix # user id, security plugins can provide their own means of doing ids. def callerid "uid=#{Process.uid}" end # Security providers should provide this, see MCollective::Security::Psk def validrequest?(req) Log.error("validrequest? is not implimented in #{self.class}") end # Security providers should provide this, see MCollective::Security::Psk def encoderequest(sender, msg, filter={}) Log.error("encoderequest is not implimented in #{self.class}") end # Security providers should provide this, see MCollective::Security::Psk def encodereply(sender, msg, requestcallerid=nil) Log.error("encodereply is not implimented in #{self.class}") end # Security providers should provide this, see MCollective::Security::Psk def decodemsg(msg) Log.error("decodemsg is not implimented in #{self.class}") end end end end mcollective-2.0.0/lib/mcollective/ssl.rb0000644000175000017500000001701711747546503017251 0ustar jonasjonasrequire 'openssl' require 'base64' require 'digest/sha1' module MCollective # A class that assists in encrypting and decrypting data using a # combination of RSA and AES # # Data will be AES encrypted for speed, the Key used in # the AES # stage will be encrypted using RSA # # ssl = SSL.new(public_key, private_key, passphrase) # # data = File.read("largefile.dat") # # crypted_data = ssl.encrypt_with_private(data) # # pp crypted_data # # This will result in a hash of data like: # # crypted = {:key => "crd4NHvG....=", # :data => "XWXlqN+i...=="} # # The key and data will all be base 64 encoded already by default # you can pass a 2nd parameter as false to encrypt_with_private and # counterparts that will prevent the base 64 encoding # # You can pass the data hash into ssl.decrypt_with_public which # should return your original data # # There are matching methods for using a public key to encrypt # data to be decrypted using a private key class SSL attr_reader :public_key_file, :private_key_file, :ssl_cipher def initialize(pubkey=nil, privkey=nil, passphrase=nil, cipher=nil) @public_key_file = pubkey @private_key_file = privkey @public_key = read_key(:public, pubkey) @private_key = read_key(:private, privkey, passphrase) @ssl_cipher = "aes-256-cbc" @ssl_cipher = Config.instance.ssl_cipher if Config.instance.ssl_cipher @ssl_cipher = cipher if cipher raise "The supplied cipher '#{@ssl_cipher}' is not supported" unless OpenSSL::Cipher.ciphers.include?(@ssl_cipher) end # Encrypts supplied data using AES and then encrypts using RSA # the key and IV # # Return a hash with everything optionally base 64 encoded def encrypt_with_public(plain_text, base64=true) crypted = aes_encrypt(plain_text) if base64 key = base64_encode(rsa_encrypt_with_public(crypted[:key])) data = base64_encode(crypted[:data]) else key = rsa_encrypt_with_public(crypted[:key]) data = crypted[:data] end {:key => key, :data => data} end # Encrypts supplied data using AES and then encrypts using RSA # the key and IV # # Return a hash with everything optionally base 64 encoded def encrypt_with_private(plain_text, base64=true) crypted = aes_encrypt(plain_text) if base64 key = base64_encode(rsa_encrypt_with_private(crypted[:key])) data = base64_encode(crypted[:data]) else key = rsa_encrypt_with_private(crypted[:key]) data = crypted[:data] end {:key => key, :data => data} end # Decrypts data, expects a hash as create with crypt_with_public def decrypt_with_private(crypted, base64=true) raise "Crypted data should include a key" unless crypted.include?(:key) raise "Crypted data should include data" unless crypted.include?(:data) if base64 key = rsa_decrypt_with_private(base64_decode(crypted[:key])) aes_decrypt(key, base64_decode(crypted[:data])) else key = rsa_decrypt_with_private(crypted[:key]) aes_decrypt(key, crypted[:data]) end end # Decrypts data, expects a hash as create with crypt_with_private def decrypt_with_public(crypted, base64=true) raise "Crypted data should include a key" unless crypted.include?(:key) raise "Crypted data should include data" unless crypted.include?(:data) if base64 key = rsa_decrypt_with_public(base64_decode(crypted[:key])) aes_decrypt(key, base64_decode(crypted[:data])) else key = rsa_decrypt_with_public(crypted[:key]) aes_decrypt(key, crypted[:data]) end end # Use the public key to RSA encrypt data def rsa_encrypt_with_public(plain_string) raise "No public key set" unless @public_key @public_key.public_encrypt(plain_string) end # Use the private key to RSA decrypt data def rsa_decrypt_with_private(crypt_string) raise "No private key set" unless @private_key @private_key.private_decrypt(crypt_string) end # Use the private key to RSA encrypt data def rsa_encrypt_with_private(plain_string) raise "No private key set" unless @private_key @private_key.private_encrypt(plain_string) end # Use the public key to RSA decrypt data def rsa_decrypt_with_public(crypt_string) raise "No public key set" unless @public_key @public_key.public_decrypt(crypt_string) end # encrypts a string, returns a hash of key, iv and data def aes_encrypt(plain_string) cipher = OpenSSL::Cipher::Cipher.new(ssl_cipher) cipher.encrypt key = cipher.random_key cipher.key = key cipher.pkcs5_keyivgen(key) encrypted_data = cipher.update(plain_string) + cipher.final {:key => key, :data => encrypted_data} end # decrypts a string given key, iv and data def aes_decrypt(key, crypt_string) cipher = OpenSSL::Cipher::Cipher.new(ssl_cipher) cipher.decrypt cipher.key = key cipher.pkcs5_keyivgen(key) decrypted_data = cipher.update(crypt_string) + cipher.final end # Signs a string using the private key def sign(string, base64=false) sig = @private_key.sign(OpenSSL::Digest::SHA1.new, string) base64 ? base64_encode(sig) : sig end # Using the public key verifies that a string was signed using the private key def verify_signature(signature, string, base64=false) signature = base64_decode(signature) if base64 @public_key.verify(OpenSSL::Digest::SHA1.new, signature, string) end # base 64 encode a string def base64_encode(string) SSL.base64_encode(string) end def self.base64_encode(string) Base64.encode64(string) end # base 64 decode a string def base64_decode(string) SSL.base64_decode(string) end def self.base64_decode(string) Base64.decode64(string) end def md5(string) SSL.md5(string) end def self.md5(string) Digest::MD5.hexdigest(string) end # Reads either a :public or :private key from disk, uses an # optional passphrase to read the private key def read_key(type, key=nil, passphrase=nil) return key if key.nil? raise "Could not find key #{key}" unless File.exist?(key) if type == :public begin key = OpenSSL::PKey::RSA.new(File.read(key)) rescue OpenSSL::PKey::RSAError key = OpenSSL::X509::Certificate.new(File.read(key)).public_key end # Ruby < 1.9.3 had a bug where it does not correctly clear the # queue of errors while reading a key. It tries various ways # to read the key and each failing attempt pushes an error onto # the queue. With pubkeys only the 3rd attempt pass leaving 2 # stale errors on the error queue. # # In 1.9.3 they fixed this by simply discarding the errors after # every attempt. So we simulate this fix here for older rubies # as without it we get SSL_read errors from the Stomp+TLS sessions # # We do this only on 1.8 relying on 1.9.3 to do the right thing # and we do not support 1.9 less than 1.9.3 # # See http://bugs.ruby-lang.org/issues/4550 OpenSSL.errors if Util.ruby_version =~ /^1.8/ return key elsif type == :private return OpenSSL::PKey::RSA.new(File.read(key), passphrase) else raise "Can only load :public or :private keys" end end end end mcollective-2.0.0/lib/mcollective/connector.rb0000644000175000017500000000130011747546503020426 0ustar jonasjonasmodule MCollective # Connectors take care of transporting messages between clients and agents, # the design doesn't your middleware to be very rich in features. All it # really needs is the ability to send and receive messages to named queues/topics. # # At present there are assumptions about the naming of topics and queues that is # compatible with Stomp, ie. # # /topic/foo.bar/baz # /queue/foo.bar/baz # # This is the only naming format that is supported, but you could replace Stomp # with something else that supports the above, see MCollective::Connector::Stomp # for the default connector. module Connector autoload :Base, "mcollective/connector/base" end end mcollective-2.0.0/lib/mcollective/registration/0000755000175000017500000000000011747546503020627 5ustar jonasjonasmcollective-2.0.0/lib/mcollective/registration/base.rb0000644000175000017500000000411411747546503022066 0ustar jonasjonasmodule MCollective module Registration # This is a base class that other registration plugins can use # to handle regular announcements to the mcollective # # The configuration file determines how often registration messages # gets sent using the _registerinterval_ option, the plugin runs in the # background in a thread. class Base # Register plugins that inherits base def self.inherited(klass) PluginManager << {:type => "registration_plugin", :class => klass.to_s} end # Creates a background thread that periodically send a registration notice. # # The actual registration notices comes from the 'body' method of the registration # plugins. def run(connection) return false if interval == 0 Thread.new do loop do begin publish(body) sleep interval rescue Exception => e Log.error("Sending registration message failed: #{e}") sleep interval end end end end def config Config.instance end def msg_filter {"agent" => "registration"} end def target_collective main_collective = config.main_collective collective = config.registration_collective || main_collective unless config.collectives.include?(collective) Log.warn("Sending registration to #{main_collective}: #{collective} is not a valid collective") collective = main_collective end return collective end def interval config.registerinterval end def publish(message) unless message Log.debug("Skipping registration due to nil body") else req = Message.new(message, nil, {:type => :request, :agent => "registration", :collective => target_collective, :filter => msg_filter}) req.encode! Log.debug("Sending registration #{req.requestid} to collective #{req.collective}") req.publish end end end end end mcollective-2.0.0/lib/mcollective/pluginpackager/0000755000175000017500000000000011747546503021111 5ustar jonasjonasmcollective-2.0.0/lib/mcollective/pluginpackager/standard_definition.rb0000644000175000017500000000463511747546503025456 0ustar jonasjonasmodule MCollective module PluginPackager class StandardDefinition attr_accessor :path, :packagedata, :metadata, :target_path, :vendor, :iteration attr_accessor :plugintype, :preinstall, :postinstall, :dependencies, :mcserver attr_accessor :mccommon def initialize(path, name, vendor, preinstall, postinstall, iteration, dependencies, mcodependency, plugintype) @plugintype = plugintype @path = path @packagedata = {} @iteration = iteration || 1 @preinstall = preinstall @postinstall = postinstall @vendor = vendor || "Puppet Labs" @dependencies = dependencies || [] @mcserver = mcodependency[:server] || "mcollective" @mccommon = mcodependency[:common] || "mcollective-common" @target_path = File.expand_path(@path) @metadata = PluginPackager.get_metadata(@path, @plugintype) @metadata[:name] = (name || @metadata[:name]).downcase.gsub(" ", "-") identify_packages end # Identify present packages and populate the packagedata hash def identify_packages common_package = common @packagedata[:common] = common_package if common_package plugin_package = plugin @packagedata[@plugintype] = plugin_package if plugin_package end # Obtain standard plugin files and dependencies def plugin plugindata = {:files => [], :dependencies => @dependencies.clone << @mcserver, :description => "#{@name} #{@plugintype} plugin for the Marionette Collective."} plugindir = File.join(@path, @plugintype.to_s) if PluginPackager.check_dir_present plugindir plugindata[:files] = Dir.glob(File.join(plugindir, "*")) else return nil end plugindata[:dependencies] <<"mcollective-#{@metadata[:name]}-common" if @packagedata[:common] plugindata end # Obtain list of common files def common common = {:files => [], :dependencies => @dependencies.clone << @mccommon, :description => "Common libraries for #{@name} connector plugin"} commondir = File.join(@path, "util") if PluginPackager.check_dir_present commondir common[:files] = Dir.glob(File.join(commondir, "*")) return common else return nil end end end end end mcollective-2.0.0/lib/mcollective/pluginpackager/agent_definition.rb0000644000175000017500000000703311747546503024747 0ustar jonasjonasmodule MCollective module PluginPackager # MCollective Agent Plugin package class AgentDefinition attr_accessor :path, :packagedata, :metadata, :target_path, :vendor, :iteration, :preinstall attr_accessor :plugintype, :dependencies, :postinstall, :mcserver, :mcclient, :mccommon def initialize(path, name, vendor, preinstall, postinstall, iteration, dependencies, mcodependency, plugintype) @plugintype = plugintype @path = path @packagedata = {} @iteration = iteration || 1 @preinstall = preinstall @postinstall = postinstall @vendor = vendor || "Puppet Labs" @mcserver = mcodependency[:server] || "mcollective" @mcclient = mcodependency[:client] || "mcollective-client" @mccommon = mcodependency[:common] || "mcollective-common" @dependencies = dependencies || [] @target_path = File.expand_path(@path) @metadata = PluginPackager.get_metadata(@path, "agent") @metadata[:name] = (name || @metadata[:name]).downcase.gsub(" ", "-") identify_packages end # Identify present packages and populate packagedata hash. def identify_packages common_package = common @packagedata[:common] = common_package if common_package agent_package = agent @packagedata[:agent] = agent_package if agent_package client_package = client @packagedata[:client] = client_package if client_package end # Obtain Agent package files and dependencies. def agent agent = {:files => [], :dependencies => @dependencies.clone << @mcserver, :description => "Agent plugin for #{@metadata[:name]}"} agentdir = File.join(@path, "agent") if PluginPackager.check_dir_present agentdir ddls = Dir.glob(File.join(agentdir, "*.ddl")) agent[:files] = (Dir.glob(File.join(agentdir, "*")) - ddls) implementations = Dir.glob(File.join(@metadata[:name], "**")) agent[:files] += implementations unless implementations.empty? else return nil end agent[:dependencies] << "mcollective-#{@metadata[:name]}-common" if @packagedata[:common] agent end # Obtain client package files and dependencies. def client client = {:files => [], :dependencies => @dependencies.clone << @mcclient, :description => "Client plugin for #{@metadata[:name]}"} clientdir = File.join(@path, "application") bindir = File.join(@path, "bin") ddldir = File.join(@path, "agent") client[:files] += Dir.glob(File.join(clientdir, "*")) if PluginPackager.check_dir_present clientdir client[:files] += Dir.glob(File.join(bindir,"*")) if PluginPackager.check_dir_present bindir client[:files] += Dir.glob(File.join(ddldir, "*.ddl")) if PluginPackager.check_dir_present ddldir client[:dependencies] << "mcollective-#{@metadata[:name]}-common" if @packagedata[:common] client[:files].empty? ? nil : client end # Obtain common package files and dependencies. def common common = {:files =>[], :dependencies => @dependencies.clone << @mccommon, :description => "Common libraries for #{@metadata[:name]}"} commondir = File.join(@path, "util") common[:files] += Dir.glob(File.join(commondir,"*")) if PluginPackager.check_dir_present commondir common[:files].empty? ? nil : common end end end end mcollective-2.0.0/lib/mcollective/facts/0000755000175000017500000000000011747546503017215 5ustar jonasjonasmcollective-2.0.0/lib/mcollective/facts/base.rb0000644000175000017500000000470111747546503020456 0ustar jonasjonasmodule MCollective module Facts # A base class for fact providers, to make a new fully functional fact provider # inherit from this and simply provide a self.get_facts method that returns a # hash like: # # {"foo" => "bar", # "bar" => "baz"} class Base def initialize @facts = {} @last_good_facts = {} @last_facts_load = 0 end # Registers new fact sources into the plugin manager def self.inherited(klass) PluginManager << {:type => "facts_plugin", :class => klass.to_s} end # Returns the value of a single fact def get_fact(fact=nil) config = Config.instance cache_time = config.fact_cache_time || 300 Thread.exclusive do begin if (Time.now.to_i - @last_facts_load > cache_time.to_i ) || force_reload? Log.debug("Resetting facter cache, now: #{Time.now.to_i} last-known-good: #{@last_facts_load}") tfacts = load_facts_from_source # Force reset to last known good state on empty facts raise "Got empty facts" if tfacts.empty? @facts.clear tfacts.each_pair do |key,value| @facts[key.to_s] = value.to_s end @last_good_facts = @facts.clone @last_facts_load = Time.now.to_i else Log.debug("Using cached facts now: #{Time.now.to_i} last-known-good: #{@last_facts_load}") end rescue Exception => e Log.error("Failed to load facts: #{e.class}: #{e}") # Avoid loops where failing fact loads cause huge CPU # loops, this way it only retries once every cache_time # seconds @last_facts_load = Time.now.to_i # Revert to last known good state @facts = @last_good_facts.clone end end # If you do not supply a specific fact all facts will be returned if fact.nil? return @facts else @facts.include?(fact) ? @facts[fact] : nil end end # Returns all facts def get_facts get_fact(nil) end # Returns true if we know about a specific fact, false otherwise def has_fact?(fact) get_fact(nil).include?(fact) end # Plugins can override this to provide forced fact invalidation def force_reload? false end end end end mcollective-2.0.0/lib/mcollective/rpc.rb0000644000175000017500000001321311747546503017226 0ustar jonasjonasrequire 'pp' module MCollective # Toolset to create a standard interface of client and agent using # an RPC metaphor, standard compliant agents will make it easier # to create generic clients like web interfaces etc module RPC autoload :Client, "mcollective/rpc/client" autoload :Agent, "mcollective/rpc/agent" autoload :Reply, "mcollective/rpc/reply" autoload :Request, "mcollective/rpc/request" autoload :Audit, "mcollective/rpc/audit" autoload :Progress, "mcollective/rpc/progress" autoload :Stats, "mcollective/rpc/stats" autoload :DDL, "mcollective/rpc/ddl" autoload :Result, "mcollective/rpc/result" autoload :Helpers, "mcollective/rpc/helpers" autoload :ActionRunner, "mcollective/rpc/actionrunner" # Creates a standard options hash, pass in a block to add extra headings etc # see Optionparser def rpcoptions oparser = MCollective::Optionparser.new({:verbose => false, :progress_bar => true}, "filter") options = oparser.parse do |parser, options| if block_given? yield(parser, options) end Helpers.add_simplerpc_options(parser, options) end return options end # Wrapper to create clients, supposed to be used as # a mixin: # # include MCollective::RPC # # exim = rpcclient("exim") # printrpc exim.mailq # # or # # rpcclient("exim") do |exim| # printrpc exim.mailq # end # # It will take a few flags: # :configfile => "etc/client.cfg" # :options => options # :exit_on_failure => true # # Options would be a build up options hash from the Optionparser # you can use the rpcoptions helper to create this # # :exit_on_failure is true by default, and causes the application to # exit if there is a failure constructing the RPC client. Set this flag # to false to cause an Exception to be raised instead. def rpcclient(agent, flags = {}) configfile = flags[:configfile] || "/etc/mcollective/client.cfg" options = flags[:options] || nil if flags.key?(:exit_on_failure) exit_on_failure = flags[:exit_on_failure] else # We exit on failure by default for CLI-friendliness exit_on_failure = true end begin if options rpc = Client.new(agent, :configfile => options[:config], :options => options) @options = rpc.options else rpc = Client.new(agent, :configfile => configfile) @options = rpc.options end rescue Exception => e if exit_on_failure puts("Could not create RPC client: #{e}") exit! else raise e end end if block_given? yield(rpc) else return rpc end end # means for other classes to drop stats into this module # its a bit hacky but needed so that the mixin methods like # printrpcstats can easily get access to it without # users having to pass it around in params. def self.stats(stats) @@stats = stats end # means for other classes to drop discovered hosts into this module # its a bit hacky but needed so that the mixin methods like # printrpcstats can easily get access to it without # users having to pass it around in params. def self.discovered(discovered) @@discovered = discovered end # Prints stats, requires stats to be saved from elsewhere # using the MCollective::RPC.stats method. # # If you've passed -v on the command line a detailed stat block # will be printed, else just a one liner. # # You can pass flags into it, at the moment only one flag is # supported: # # printrpcstats :caption => "Foo" # # This will use "Foo" as the caption to the stats in verbose # mode def printrpcstats(flags={}) return unless @options[:output_format] == :console verbose = @options[:verbose] rescue verbose = false caption = flags[:caption] || "rpc stats" begin stats = @@stats rescue puts("no stats to display") return end puts puts stats.report(caption, verbose) end # Prints the result of an RPC call. # # In the default quiet mode - no flattening or verbose - only results # that produce an error will be printed # # To get details of each result run with the -v command line option. def printrpc(result, flags = {}) verbose = @options[:verbose] rescue verbose = false verbose = flags[:verbose] || verbose flatten = flags[:flatten] || false format = @options[:output_format] result_text = Helpers.rpcresults(result, {:verbose => verbose, :flatten => flatten, :format => format}) if result.is_a?(Array) && format == :console puts "\n%s\n" % [ result_text ] else # when we get just one result to print dont pad them all with # blank spaces etc, just print the individual result with no # padding puts result_text unless result_text == "" end end # Wrapper for MCollective::Util.empty_filter? to make clients less fugly # to write - ticket #18 def empty_filter?(options) if options.include?(:filter) Util.empty_filter?(options[:filter]) else Util.empty_filter?(options) end end # Factory for RPC::Request messages, only really here to make agents # a bit easier to understand def self.request(msg) RPC::Request.new(msg) end # Factory for RPC::Reply messages, only really here to make agents # a bit easier to understand def self.reply RPC::Reply.new end end end mcollective-2.0.0/lib/mcollective/logger.rb0000644000175000017500000000013311747546503017716 0ustar jonasjonasmodule MCollective module Logger autoload :Base, "mcollective/logger/base" end end mcollective-2.0.0/lib/mcollective/rpc/0000755000175000017500000000000011747546503016701 5ustar jonasjonasmcollective-2.0.0/lib/mcollective/rpc/helpers.rb0000644000175000017500000002554611747546503020704 0ustar jonasjonasmodule MCollective module RPC # Various utilities for the RPC system class Helpers # Checks in PATH returns true if the command is found def self.command_in_path?(command) found = ENV["PATH"].split(File::PATH_SEPARATOR).map do |p| File.exist?(File.join(p, command)) end found.include?(true) end # Parse JSON output as produced by printrpc and extract # the "sender" of each rpc response # # The simplist valid JSON based data would be: # # [ # {"sender" => "example.com"}, # {"sender" => "another.com"} # ] def self.extract_hosts_from_json(json) hosts = JSON.parse(json) raise "JSON hosts list is not an array" unless hosts.is_a?(Array) hosts.map do |host| raise "JSON host list is not an array of Hashes" unless host.is_a?(Hash) raise "JSON host list does not have senders in it" unless host.include?("sender") host["sender"] end.uniq end # Given an array of something, make sure each is a string # chomp off any new lines and return just the array of hosts def self.extract_hosts_from_array(hosts) [hosts].flatten.map do |host| raise "#{host} should be a string" unless host.is_a?(String) host.chomp end end # Figures out the columns and liens of the current tty # # Returns [0, 0] if it can't figure it out or if you're # not running on a tty def self.terminal_dimensions return [0, 0] unless STDOUT.tty? return [80, 40] if Util.windows? if ENV["COLUMNS"] && ENV["LINES"] return [ENV["COLUMNS"].to_i, ENV["LINES"].to_i] elsif ENV["TERM"] && command_in_path?("tput") return [`tput cols`.to_i, `tput lines`.to_i] elsif command_in_path?('stty') return `stty size`.scan(/\d+/).map {|s| s.to_i } else return [0, 0] end rescue [0, 0] end # Return color codes, if the config color= option is false # just return a empty string def self.color(code) colorize = Config.instance.color colors = {:red => "", :green => "", :yellow => "", :cyan => "", :bold => "", :reset => ""} if colorize return colors[code] || "" else return "" end end # Helper to return a string in specific color def self.colorize(code, msg) "#{self.color(code)}#{msg}#{self.color(:reset)}" end # Returns a blob of text representing the results in a standard way # # It tries hard to do sane things so you often # should not need to write your own display functions # # If the agent you are getting results for has a DDL # it will use the hints in there to do the right thing specifically # it will look at the values of display in the DDL to choose # when to show results # # If you do not have a DDL you can pass these flags: # # printrpc exim.mailq, :flatten => true # printrpc exim.mailq, :verbose => true # # If you've asked it to flatten the result it will not print sender # hostnames, it will just print the result as if it's one huge result, # handy for things like showing a combined mailq. def self.rpcresults(result, flags = {}) flags = {:verbose => false, :flatten => false, :format => :console}.merge(flags) result_text = "" ddl = nil # if running in verbose mode, just use the old style print # no need for all the DDL helpers obfuscating the result if flags[:format] == :json if STDOUT.tty? result_text = JSON.pretty_generate(result) else result_text = result.to_json end else if flags[:verbose] result_text = old_rpcresults(result, flags) else [result].flatten.each do |r| begin ddl ||= DDL.new(r.agent).action_interface(r.action.to_s) sender = r[:sender] status = r[:statuscode] message = r[:statusmsg] display = ddl[:display] result = r[:data] # appand the results only according to what the DDL says case display when :ok if status == 0 result_text << text_for_result(sender, status, message, result, ddl) end when :failed if status > 0 result_text << text_for_result(sender, status, message, result, ddl) end when :always result_text << text_for_result(sender, status, message, result, ddl) when :flatten result_text << text_for_flattened_result(status, result) end rescue Exception => e # no DDL so just do the old style print unchanged for # backward compat result_text = old_rpcresults(result, flags) end end end end result_text end # Return text representing a result def self.text_for_result(sender, status, msg, result, ddl) statusses = ["", colorize(:red, "Request Aborted"), colorize(:yellow, "Unknown Action"), colorize(:yellow, "Missing Request Data"), colorize(:yellow, "Invalid Request Data"), colorize(:red, "Unknown Request Status")] result_text = "%-40s %s\n" % [sender, statusses[status]] result_text << " %s\n" % [colorize(:yellow, msg)] unless msg == "OK" # only print good data, ignore data that results from failure if [0, 1].include?(status) if result.is_a?(Hash) # figure out the lengths of the display as strings, we'll use # it later to correctly justify the output lengths = result.keys.map do |k| begin ddl[:output][k][:display_as].size rescue k.to_s.size end end result.keys.each do |k| # get all the output fields nicely lined up with a # 3 space front padding begin display_as = ddl[:output][k][:display_as] rescue display_as = k.to_s end display_length = display_as.size padding = lengths.max - display_length + 3 result_text << " " * padding result_text << "#{display_as}:" if [String, Numeric].include?(result[k].class) lines = result[k].to_s.split("\n") if lines.empty? result_text << "\n" else lines.each_with_index do |line, i| i == 0 ? padtxt = " " : padtxt = " " * (padding + display_length + 2) result_text << "#{padtxt}#{line}\n" end end else padding = " " * (lengths.max + 5) result_text << " " << result[k].pretty_inspect.split("\n").join("\n" << padding) << "\n" end end else result_text << "\n\t" + result.pretty_inspect.split("\n").join("\n\t") end end result_text << "\n" result_text end # Returns text representing a flattened result of only good data def self.text_for_flattened_result(status, result) result_text = "" if status <= 1 unless result.is_a?(String) result_text << result.pretty_inspect else result_text << result end end end # Backward compatible display block for results without a DDL def self.old_rpcresults(result, flags = {}) result_text = "" if flags[:flatten] result.each do |r| if r[:statuscode] <= 1 data = r[:data] unless data.is_a?(String) result_text << data.pretty_inspect else result_text << data end else result_text << r.pretty_inspect end end result_text << "" else [result].flatten.each do |r| if flags[:verbose] result_text << "%-40s: %s\n" % [r[:sender], r[:statusmsg]] if r[:statuscode] <= 1 r[:data].pretty_inspect.split("\n").each {|m| result_text += " #{m}"} result_text << "\n\n" elsif r[:statuscode] == 2 # dont print anything, no useful data to display # past what was already shown elsif r[:statuscode] == 3 # dont print anything, no useful data to display # past what was already shown elsif r[:statuscode] == 4 # dont print anything, no useful data to display # past what was already shown else result_text << " #{r[:statusmsg]}" end else unless r[:statuscode] == 0 result_text << "%-40s %s\n" % [r[:sender], colorize(:red, r[:statusmsg])] end end end end result_text << "" end # Add SimpleRPC common options def self.add_simplerpc_options(parser, options) parser.separator "" # add SimpleRPC specific options to all clients that use our library parser.on('--np', '--no-progress', 'Do not show the progress bar') do |v| options[:progress_bar] = false end parser.on('--one', '-1', 'Send request to only one discovered nodes') do |v| options[:mcollective_limit_targets] = 1 end parser.on('--batch SIZE', Integer, 'Do requests in batches') do |v| options[:batch_size] = v end parser.on('--batch-sleep SECONDS', Float, 'Sleep time between batches') do |v| options[:batch_sleep_time] = v end parser.on('--limit-nodes COUNT', '--ln', 'Send request to only a subset of nodes, can be a percentage') do |v| raise "Invalid limit specified: #{v} valid limits are /^\d+%*$/" unless v =~ /^\d+%*$/ if v =~ /^\d+$/ options[:mcollective_limit_targets] = v.to_i else options[:mcollective_limit_targets] = v end end parser.on('--json', '-j', 'Produce JSON output') do |v| options[:progress_bar] = false options[:output_format] = :json end end end end end mcollective-2.0.0/lib/mcollective/rpc/stats.rb0000644000175000017500000001321711747546503020370 0ustar jonasjonasmodule MCollective module RPC # Class to wrap all the stats and to keep track of some timings class Stats attr_accessor :noresponsefrom, :starttime, :discoverytime, :blocktime, :responses, :totaltime attr_accessor :discovered, :discovered_nodes, :okcount, :failcount, :noresponsefrom, :responsesfrom def initialize reset end # Resets stats, if discovery time is set we keep it as it was def reset @noresponsefrom = [] @responsesfrom = [] @responses = 0 @starttime = Time.now.to_f @discoverytime = 0 unless @discoverytime @blocktime = 0 @totaltime = 0 @discovered = 0 @discovered_nodes = [] @okcount = 0 @failcount = 0 @noresponsefrom = [] end # returns a hash of our stats def to_hash {:noresponsefrom => @noresponsefrom, :starttime => @starttime, :discoverytime => @discoverytime, :blocktime => @blocktime, :responses => @responses, :totaltime => @totaltime, :discovered => @discovered, :discovered_nodes => @discovered_nodes, :noresponsefrom => @noresponsefrom, :okcount => @okcount, :failcount => @failcount} end # Fake hash access to keep things backward compatible def [](key) to_hash[key] rescue nil end # increment the count of ok hosts def ok @okcount += 1 rescue @okcount = 1 end # increment the count of failed hosts def fail @failcount += 1 rescue @failcount = 1 end # Re-initializes the object with stats from the basic client def client_stats=(stats) @noresponsefrom = stats[:noresponsefrom] @responses = stats[:responses] @starttime = stats[:starttime] @blocktime = stats[:blocktime] @totaltime = stats[:totaltime] @discoverytime = stats[:discoverytime] if @discoverytime == 0 end # Utility to time discovery from :start to :end def time_discovery(action) if action == :start @discovery_start = Time.now.to_f elsif action == :end @discoverytime = Time.now.to_f - @discovery_start else raise("Uknown discovery action #{action}") end rescue @discoverytime = 0 end # helper to time block execution time def time_block_execution(action) if action == :start @block_start = Time.now.to_f elsif action == :end @blocktime += Time.now.to_f - @block_start else raise("Uknown block action #{action}") end rescue @blocktime = 0 end # Update discovered and discovered_nodes based on # discovery results def discovered_agents(agents) @discovered_nodes = agents @discovered = agents.size end # Helper to calculate total time etc def finish_request @totaltime = @blocktime + @discoverytime # figures out who we had no responses from dhosts = @discovered_nodes.clone @responsesfrom.each {|r| dhosts.delete(r)} @noresponsefrom = dhosts rescue @totaltime = 0 @noresponsefrom = [] end # Helper to keep track of who we received responses from def node_responded(node) @responsesfrom << node rescue @responsesfrom = [node] end # Returns a blob of text representing the request status based on the # stats contained in this class def report(caption = "rpc stats", verbose = false) result_text = [] if verbose result_text << Helpers.colorize(:yellow, "---- #{caption} ----") if @discovered @responses < @discovered ? color = :red : color = :reset result_text << " Nodes: %s / %s" % [ Helpers.colorize(color, @discovered), Helpers.colorize(color, @responses) ] else result_text << " Nodes: #{@responses}" end @failcount < 0 ? color = :red : color = :reset result_text << " Pass / Fail: %s / %s" % [Helpers.colorize(color, @okcount), Helpers.colorize(color, @failcount) ] result_text << " Start Time: %s" % [Time.at(@starttime)] result_text << " Discovery Time: %.2fms" % [@discoverytime * 1000] result_text << " Agent Time: %.2fms" % [@blocktime * 1000] result_text << " Total Time: %.2fms" % [@totaltime * 1000] else if @discovered @responses < @discovered ? color = :red : color = :green result_text << "Finished processing %s / %s hosts in %.2f ms" % [Helpers.colorize(color, @responses), Helpers.colorize(color, @discovered), @blocktime * 1000] else result_text << "Finished processing %s hosts in %.2f ms" % [Helpers.colorize(:bold, @responses), @blocktime * 1000] end end if no_response_report != "" result_text << "" << no_response_report end result_text.join("\n") end # Returns a blob of text indicating what nodes did not respond def no_response_report result_text = [] if @noresponsefrom.size > 0 result_text << Helpers.colorize(:red, "\nNo response from:\n") @noresponsefrom.each_with_index do |c,i| result_text << "" if i % 4 == 0 result_text << "%30s" % [c] end result_text << "" end result_text.join("\n") end end end end mcollective-2.0.0/lib/mcollective/rpc/result.rb0000644000175000017500000000175411747546503020553 0ustar jonasjonasmodule MCollective module RPC # Simple class to manage compliant results from MCollective::RPC agents # # Currently it just fakes Hash behaviour to the result to remain backward # compatible but it also knows which agent and action produced it so you # can associate results to a DDL class Result attr_reader :agent, :action, :results include Enumerable def initialize(agent, action, result={}) @agent = agent @action = action @results = result end def [](idx) @results[idx] end def []=(idx, item) @results[idx] = item end def each @results.each_pair {|k,v| yield(k,v) } end def to_json(*a) {:agent => @agent, :action => @action, :sender => @results[:sender], :statuscode => @results[:statuscode], :statusmsg => @results[:statusmsg], :data => @results[:data]}.to_json(*a) end end end end mcollective-2.0.0/lib/mcollective/rpc/agent.rb0000644000175000017500000003647111747546503020337 0ustar jonasjonasmodule MCollective module RPC # A wrapper around the traditional agent, it takes care of a lot of the tedious setup # you would do for each agent allowing you to just create methods following a naming # standard leaving the heavy lifting up to this clas. # # See http://marionette-collective.org/simplerpc/agents.html # # It only really makes sense to use this with a Simple RPC client on the other end, basic # usage would be: # # module MCollective # module Agent # class Helloworld "Test SimpleRPC Agent", # :description => "A simple test", # :author => "You", # :license => "1.1", # :url => "http://your.com/, # :timeout => 60 # # action "hello" do # reply[:msg] = "Hello #{request[:name]}" # end # # action "foo" do # implemented_by "/some/script.sh" # end # end # end # end # # If you wish to implement the logic for an action using an external script use the # implemented_by method that will cause your script to be run with 2 arguments. # # The first argument is a file containing JSON with the request and the 2nd argument # is where the script should save its output as a JSON hash. # # We also currently have the validation code in here, this will be moved to plugins soon. class Agent attr_accessor :meta, :reply, :request attr_reader :logger, :config, :timeout, :ddl def initialize # Default meta data unset @meta = {:timeout => 10, :name => "Unknown", :description => "Unknown", :author => "Unknown", :license => "Unknown", :version => "Unknown", :url => "Unknown"} @timeout = meta[:timeout] || 10 @logger = Log.instance @config = Config.instance @agent_name = self.class.to_s.split("::").last.downcase # Loads the DDL so we can later use it for validation # and help generation begin @ddl = DDL.new(@agent_name) rescue Exception => e Log.debug("Failed to load DDL for agent: #{e.class}: #{e}") @ddl = nil end # if we have a global authorization provider enable it # plugins can still override it per plugin self.class.authorized_by(@config.rpcauthprovider) if @config.rpcauthorization startup_hook end def handlemsg(msg, connection) @request = RPC.request(msg) @reply = RPC.reply begin # Calls the authorization plugin if any is defined # if this raises an exception we wil just skip processing this # message authorization_hook(@request) if respond_to?("authorization_hook") # Audits the request, currently continues processing the message # we should make this a configurable so that an audit failure means # a message wont be processed by this node depending on config audit_request(@request, connection) before_processing_hook(msg, connection) if respond_to?("#{@request.action}_action") send("#{@request.action}_action") else raise UnknownRPCAction, "Unknown action: #{@request.action}" end rescue RPCAborted => e @reply.fail e.to_s, 1 rescue UnknownRPCAction => e @reply.fail e.to_s, 2 rescue MissingRPCData => e @reply.fail e.to_s, 3 rescue InvalidRPCData => e @reply.fail e.to_s, 4 rescue UnknownRPCError => e @reply.fail e.to_s, 5 rescue Exception => e @reply.fail e.to_s, 5 end after_processing_hook if @request.should_respond? return @reply.to_hash else Log.debug("Client did not request a response, surpressing reply") return nil end end # By default RPC Agents support a toggle in the configuration that # can enable and disable them based on the agent name # # Example an agent called Foo can have: # # plugin.foo.activate_agent = false # # and this will prevent the agent from loading on this particular # machine. # # Agents can use the activate_when helper to override this for example: # # activate_when do # File.exist?("/usr/bin/puppet") # end def self.activate? agent_name = self.to_s.split("::").last.downcase Log.debug("Starting default activation checks for #{agent_name}") should_activate = Config.instance.pluginconf["#{agent_name}.activate_agent"] if should_activate Log.debug("Found plugin config #{agent_name}.activate_agent with value #{should_activate}") unless should_activate =~ /^1|y|true$/ return false end end return true end # Generates help using the template based on the data # created with metadata and input def self.help(template) if @ddl @ddl.help(template) else "No DDL defined" end end # to auto generate help def help self.help("#{@config[:configdir]}/rpc-help.erb") end # Returns an array of actions this agent support def self.actions public_instance_methods.sort.grep(/_action$/).map do |method| $1 if method =~ /(.+)_action$/ end end private # Runs a command via the MC::Shell wrapper, options are as per MC::Shell # # The simplest use is: # # out = "" # err = "" # status = run("echo 1", :stdout => out, :stderr => err) # # reply[:out] = out # reply[:error] = err # reply[:exitstatus] = status # # This can be simplified as: # # reply[:exitstatus] = run("echo 1", :stdout => :out, :stderr => :error) # # You can set a command specific environment and cwd: # # run("echo 1", :cwd => "/tmp", :environment => {"FOO" => "BAR"}) # # This will run 'echo 1' from /tmp with FOO=BAR in addition to a setting forcing # LC_ALL = C. To prevent LC_ALL from being set either set it specifically or: # # run("echo 1", :cwd => "/tmp", :environment => nil) # # Exceptions here will be handled by the usual agent exception handler or any # specific one you create, if you dont it will just fall through and be sent # to the client. # # If the shell handler fails to return a Process::Status instance for exit # status this method will return -1 as the exit status def run(command, options={}) shellopts = {} # force stderr and stdout to be strings as the library # will append data to them if given using the << method. # # if the data pased to :stderr or :stdin is a Symbol # add that into the reply hash with that Symbol [:stderr, :stdout].each do |k| if options.include?(k) if options[k].is_a?(Symbol) reply[ options[k] ] = "" shellopts[k] = reply[ options[k] ] else if options[k].respond_to?("<<") shellopts[k] = options[k] else reply.fail! "#{k} should support << while calling run(#{command})" end end end end [:stdin, :cwd, :environment].each do |k| if options.include?(k) shellopts[k] = options[k] end end shell = Shell.new(command, shellopts) shell.runcommand if options[:chomp] shellopts[:stdout].chomp! if shellopts[:stdout].is_a?(String) shellopts[:stderr].chomp! if shellopts[:stderr].is_a?(String) end shell.status.exitstatus rescue -1 end # Registers meta data for the introspection hash def self.metadata(data) [:name, :description, :author, :license, :version, :url, :timeout].each do |arg| raise "Metadata needs a :#{arg}" unless data.include?(arg) end # Our old style agents were able to do all sorts of things to the meta # data during startup_hook etc, don't really want that but also want # backward compat. # # Here if you're using the new metadata way this replaces the getter # with one that always return the same data, setter will still work but # wont actually do anything of note. define_method("meta") { data } end # Creates the needed activate? class in a manner similar to the other # helpers like action, authorized_by etc # # activate_when do # File.exist?("/usr/bin/puppet") # end def self.activate_when(&block) (class << self; self; end).instance_eval do define_method("activate?", &block) end end # Creates a new action with the block passed and sets some defaults # # action "status" do # # logic here to restart service # end def self.action(name, &block) raise "Need to pass a body for the action" unless block_given? self.module_eval { define_method("#{name}_action", &block) } end # Helper that creates a method on the class that will call your authorization # plugin. If your plugin raises an exception that will abort the request def self.authorized_by(plugin) plugin = plugin.to_s.capitalize # turns foo_bar into FooBar plugin = plugin.to_s.split("_").map {|v| v.capitalize}.join pluginname = "MCollective::Util::#{plugin}" PluginManager.loadclass(pluginname) unless MCollective::Util.constants.include?(plugin) class_eval(" def authorization_hook(request) #{pluginname}.authorize(request) end ") end # Validates a data member, if validation is a regex then it will try to match it # else it supports testing object types only: # # validate :msg, String # validate :msg, /^[\w\s]+$/ # # There are also some special helper validators: # # validate :command, :shellsafe # validate :command, :ipv6address # validate :command, :ipv4address # validate :command, :boolean # validate :command, ["start", "stop"] # # It will raise appropriate exceptions that the RPC system understand # # TODO: this should be plugins, 1 per validatin method so users can add their own # at the moment i have it here just to proof the point really def validate(key, validation) raise MissingRPCData, "please supply a #{key} argument" unless @request.include?(key) if validation.is_a?(Regexp) raise InvalidRPCData, "#{key} should match #{validation}" unless @request[key].match(validation) elsif validation.is_a?(Symbol) case validation when :shellsafe raise InvalidRPCData, "#{key} should be a String" unless @request[key].is_a?(String) ['`', '$', ';', '|', '&&', '>', '<'].each do |chr| raise InvalidRPCData, "#{key} should not have #{chr} in it" if @request[key].match(Regexp.escape(chr)) end when :ipv6address begin require 'ipaddr' ip = IPAddr.new(@request[key]) raise InvalidRPCData, "#{key} should be an ipv6 address" unless ip.ipv6? rescue raise InvalidRPCData, "#{key} should be an ipv6 address" end when :ipv4address begin require 'ipaddr' ip = IPAddr.new(@request[key]) raise InvalidRPCData, "#{key} should be an ipv4 address" unless ip.ipv4? rescue raise InvalidRPCData, "#{key} should be an ipv4 address" end when :boolean raise InvalidRPCData, "#{key} should be boolean" unless [TrueClass, FalseClass].include?(@request[key].class) end elsif validation.is_a?(Array) raise InvalidRPCData, "#{key} should be one of %s" % [ validation.join(", ") ] unless validation.include?(@request[key]) else raise InvalidRPCData, "#{key} should be a #{validation}" unless @request[key].is_a?(validation) end end # convenience wrapper around Util#shellescape def shellescape(str) Util.shellescape(str) end # handles external actions def implemented_by(command, type=:json) runner = ActionRunner.new(command, request, type) res = runner.run reply.fail! "Did not receive data from #{command}" unless res.include?(:data) reply.fail! "Reply data from #{command} is not a Hash" unless res[:data].is_a?(Hash) reply.data.merge!(res[:data]) if res[:exitstatus] > 0 reply.fail "Failed to run #{command}: #{res[:stderr]}", res[:exitstatus] end rescue Exception => e Log.warn("Unhandled #{e.class} exception during #{request.agent}##{request.action}: #{e}") reply.fail! "Unexpected failure calling #{command}: #{e.class}: #{e}" end # Called at the end of the RPC::Agent standard initialize method # use this to adjust meta parameters, timeouts and any setup you # need to do. # # This will not be called right when the daemon starts up, we use # lazy loading and initialization so it will only be called the first # time a request for this agent arrives. def startup_hook end # Called just after a message was received from the middleware before # it gets passed to the handlers. @request and @reply will already be # set, the msg passed is the message as received from the normal # mcollective runner and the connection is the actual connector. def before_processing_hook(msg, connection) end # Called at the end of processing just before the response gets sent # to the middleware. # # This gets run outside of the main exception handling block of the agent # so you should handle any exceptions you could raise yourself. The reason # it is outside of the block is so you'll have access to even status codes # set by the exception handlers. If you do raise an exception it will just # be passed onto the runner and processing will fail. def after_processing_hook end # Gets called right after a request was received and calls audit plugins # # Agents can disable auditing by just overriding this method with a noop one # this might be useful for agents that gets a lot of requests or simply if you # do not care for the auditing in a specific agent. def audit_request(msg, connection) PluginManager["rpcaudit_plugin"].audit_request(msg, connection) if @config.rpcaudit rescue Exception => e Log.warn("Audit failed - #{e} - continuing to process message") end end end end mcollective-2.0.0/lib/mcollective/rpc/reply.rb0000644000175000017500000000242011747546503020357 0ustar jonasjonasmodule MCollective module RPC # Simple class to manage compliant replies to MCollective::RPC class Reply attr_accessor :statuscode, :statusmsg, :data def initialize @data = {} @statuscode = 0 @statusmsg = "OK" end # Helper to fill in statusmsg and code on failure def fail(msg, code=1) @statusmsg = msg @statuscode = code end # Helper that fills in statusmsg and code but also raises an appropriate error def fail!(msg, code=1) @statusmsg = msg @statuscode = code case code when 1 raise RPCAborted, msg when 2 raise UnknownRPCAction, msg when 3 raise MissingRPCData, msg when 4 raise InvalidRPCData, msg else raise UnknownRPCError, msg end end # Write to the data hash def []=(key, val) @data[key] = val end # Read from the data hash def [](key) @data[key] end # Returns a compliant Hash of the reply that should be sent # over the middleware def to_hash return {:statuscode => @statuscode, :statusmsg => @statusmsg, :data => @data} end end end end mcollective-2.0.0/lib/mcollective/rpc/request.rb0000644000175000017500000000265211747546503020723 0ustar jonasjonasmodule MCollective module RPC # Simple class to manage compliant requests for MCollective::RPC agents class Request attr_accessor :time, :action, :data, :sender, :agent, :uniqid, :caller def initialize(msg) @time = msg[:msgtime] @action = msg[:body][:action] @data = msg[:body][:data] @sender = msg[:senderid] @agent = msg[:body][:agent] @uniqid = msg[:requestid] @caller = msg[:callerid] || "unknown" end # If data is a hash, quick helper to get access to it's include? method # else returns false def include?(key) return false unless @data.is_a?(Hash) return @data.include?(key) end # If no :process_results is specified always respond else respond # based on the supplied property def should_respond? return @data[:process_results] if @data.include?(:process_results) return true end # If data is a hash, gives easy access to its members, else returns nil def [](key) return nil unless @data.is_a?(Hash) return @data[key] end def to_hash return {:agent => @agent, :action => @action, :data => @data} end def to_json to_hash.merge!({:sender => @sender, :callerid => @callerid, :uniqid => @uniqid}).to_json end end end end mcollective-2.0.0/lib/mcollective/rpc/actionrunner.rb0000644000175000017500000001021111747546503021730 0ustar jonasjonasmodule MCollective module RPC # A helper used by RPC::Agent#implemented_by to delegate an action to # an external script. At present only JSON based serialization is # supported in future ones based on key=val pairs etc will be added # # It serializes the request object into an input file and creates an # empty output file. It then calls the external command reading the # output file at the end. # # any STDERR gets logged at error level and any STDOUT gets logged at # info level. # # It will interpret the exit code from the application the same way # RPC::Reply#fail! and #fail handles their codes creating a consistent # interface, the message part of the fail message will come from STDERR # # Generally externals should just exit with code 1 on failure and print to # STDERR, this is exactly what Perl die() does and translates perfectly # to our model # # It uses the MCollective::Shell wrapper to call the external application class ActionRunner attr_reader :command, :agent, :action, :format, :stdout, :stderr, :request def initialize(command, request, format=:json) @agent = request.agent @action = request.action @format = format @request = request @command = path_to_command(command) @stdout = "" @stderr = "" end def run unless canrun?(command) Log.warn("Cannot run #{to_s}") raise RPCAborted, "Cannot execute #{to_s}" end Log.debug("Running #{to_s}") request_file = saverequest(request) reply_file = tempfile("reply") reply_file.close runner = shell(command, request_file.path, reply_file.path) runner.runcommand Log.debug("#{command} exited with #{runner.status.exitstatus}") stderr.each_line {|l| Log.error("#{to_s}: #{l.chomp}")} unless stderr.empty? stdout.each_line {|l| Log.info("#{to_s}: #{l.chomp}")} unless stdout.empty? {:exitstatus => runner.status.exitstatus, :stdout => runner.stdout, :stderr => runner.stderr, :data => load_results(reply_file.path)} ensure request_file.close! if request_file.respond_to?("close!") reply_file.close! if reply_file.respond_to?("close") end def shell(command, infile, outfile) env = {"MCOLLECTIVE_REQUEST_FILE" => infile, "MCOLLECTIVE_REPLY_FILE" => outfile} Shell.new("#{command} #{infile} #{outfile}", :cwd => Dir.tmpdir, :stdout => stdout, :stderr => stderr, :environment => env) end def load_results(file) Log.debug("Attempting to load results in #{format} format from #{file}") data = {} if respond_to?("load_#{format}_results") tempdata = send("load_#{format}_results", file) tempdata.each_pair do |k,v| data[k.to_sym] = v end end data rescue Exception => e {} end def load_json_results(file) return {} unless File.readable?(file) JSON.load(File.read(file)) rescue JSON::ParserError {} end def saverequest(req) Log.debug("Attempting to save request in #{format} format") if respond_to?("save_#{format}_request") data = send("save_#{format}_request", req) request_file = tempfile("request") request_file.puts data request_file.close end request_file end def save_json_request(req) req.to_json end def canrun?(command) File.executable?(command) end def to_s "%s#%s command: %s" % [ agent, action, command ] end def tempfile(prefix) Tempfile.new("mcollective_#{prefix}", Dir.tmpdir) end def path_to_command(command) unless command[0,1] == File::SEPARATOR Config.instance.libdir.each do |libdir| command_file = File.join(libdir, "agent", agent, command) return command_file if File.exist?(command_file) end end return command end end end end mcollective-2.0.0/lib/mcollective/rpc/progress.rb0000644000175000017500000000324511747546503021076 0ustar jonasjonasmodule MCollective module RPC # Class that shows a progress bar, currently only supports a twirling # progress bar. # # You can specify a size for the progress bar if you want if you dont # it will use the helper functions to figure out terminal dimensions # and draw an appropriately sized bar # # p = Progress.new # 100.times {|i| print p.twirl(i+1, 100) + "\r"};puts # # * [ ==================================================> ] 100 / 100 class Progress def initialize(size=nil) @twirl = ['|', '/', '-', "\\", '|', '/', '-', "\\"] @twirldex = 0 if size @size = size else cols = Helpers.terminal_dimensions[0] - 22 # Defaults back to old behavior if it # couldn't figure out the size or if # its more than 60 wide if cols <= 0 @size = 0 elsif cols > 60 @size = 60 else @size = cols end end end def twirl(current, total) # if the size is negative there is just not enough # space on the terminal, return a simpler version return "\r#{current} / #{total}" if @size == 0 if current == total txt = "\r " + Helpers.colorize(:green, "*") + " [ " else txt = "\r #{@twirl[@twirldex]} [ " end dashes = ((current.to_f / total) * @size).round dashes.times { txt << "=" } txt << ">" (@size - dashes).times { txt << " " } txt << " ] #{current} / #{total}" @twirldex == 7 ? @twirldex = 0 : @twirldex += 1 return txt end end end end mcollective-2.0.0/lib/mcollective/rpc/client.rb0000644000175000017500000007234111747546503020513 0ustar jonasjonasmodule MCollective module RPC # The main component of the Simple RPC client system, this wraps around MCollective::Client # and just brings in a lot of convention and standard approached. class Client attr_accessor :discovery_timeout, :timeout, :verbose, :filter, :config, :progress, :ttl, :reply_to attr_reader :client, :stats, :ddl, :agent, :limit_targets, :limit_method, :output_format, :batch_size, :batch_sleep_time, :batch_mode @@initial_options = nil # Creates a stub for a remote agent, you can pass in an options array in the flags # which will then be used else it will just create a default options array with # filtering enabled based on the standard command line use. # # rpc = RPC::Client.new("rpctest", :configfile => "client.cfg", :options => options) # # You typically would not call this directly you'd use MCollective::RPC#rpcclient instead # which is a wrapper around this that can be used as a Mixin def initialize(agent, flags = {}) if flags.include?(:options) initial_options = flags[:options] elsif @@initial_options initial_options = Marshal.load(@@initial_options) else oparser = MCollective::Optionparser.new({:verbose => false, :progress_bar => true, :mcollective_limit_targets => false, :batch_size => nil, :batch_sleep_time => 1}, "filter") initial_options = oparser.parse do |parser, opts| if block_given? yield(parser, opts) end Helpers.add_simplerpc_options(parser, opts) end @@initial_options = Marshal.dump(initial_options) end @stats = Stats.new @agent = agent @discovery_timeout = initial_options[:disctimeout] @timeout = initial_options[:timeout] @verbose = initial_options[:verbose] @filter = initial_options[:filter] @config = initial_options[:config] @discovered_agents = nil @progress = initial_options[:progress_bar] @limit_targets = initial_options[:mcollective_limit_targets] @limit_method = Config.instance.rpclimitmethod @output_format = initial_options[:output_format] || :console @force_direct_request = false @reply_to = initial_options[:reply_to] @batch_size = Integer(initial_options[:batch_size] || 0) @batch_sleep_time = Float(initial_options[:batch_sleep_time] || 1) @batch_mode = @batch_size > 0 agent_filter agent @client = MCollective::Client.new(@config) @client.options = initial_options @collective = @client.collective @ttl = initial_options[:ttl] || Config.instance.ttl # if we can find a DDL for the service override # the timeout of the client so we always magically # wait appropriate amounts of time. # # We add the discovery timeout to the ddl supplied # timeout as the discovery timeout tends to be tuned # for local network conditions and fact source speed # which would other wise not be accounted for and # some results might get missed. # # We do this only if the timeout is the default 5 # seconds, so that users cli overrides will still # get applied begin @ddl = DDL.new(agent) @timeout = @ddl.meta[:timeout] + @discovery_timeout if @timeout == 5 rescue Exception => e Log.debug("Could not find DDL: #{e}") @ddl = nil end # allows stderr and stdout to be overridden for testing # but also for web apps that might not want a bunch of stuff # generated to actual file handles if initial_options[:stderr] @stderr = initial_options[:stderr] else @stderr = STDERR @stderr.sync = true end if initial_options[:stdout] @stdout = initial_options[:stdout] else @stdout = STDOUT @stdout.sync = true end end # Disconnects cleanly from the middleware def disconnect @client.disconnect end # Returns help for an agent if a DDL was found def help(template) if @ddl @ddl.help(template) else return "Can't find DDL for agent '#{@agent}'" end end # Creates a suitable request hash for the SimpleRPC agent. # # You'd use this if you ever wanted to take care of sending # requests on your own - perhaps via Client#sendreq if you # didn't care for responses. # # In that case you can just do: # # msg = your_rpc.new_request("some_action", :foo => :bar) # filter = your_rpc.filter # # your_rpc.client.sendreq(msg, msg[:agent], filter) # # This will send a SimpleRPC request to the action some_action # with arguments :foo = :bar, it will return immediately and # you will have no indication at all if the request was receieved or not # # Clearly the use of this technique should be limited and done only # if your code requires such a thing def new_request(action, data) callerid = PluginManager["security_plugin"].callerid raise 'callerid received from security plugin is not valid' unless PluginManager["security_plugin"].valid_callerid?(callerid) {:agent => @agent, :action => action, :caller => callerid, :data => data} end # Magic handler to invoke remote methods # # Once the stub is created using the constructor or the RPC#rpcclient helper you can # call remote actions easily: # # ret = rpc.echo(:msg => "hello world") # # This will call the 'echo' action of the 'rpctest' agent and return the result as an array, # the array will be a simplified result set from the usual full MCollective::Client#req with # additional error codes and error text: # # { # :sender => "remote.box.com", # :statuscode => 0, # :statusmsg => "OK", # :data => "hello world" # } # # If :statuscode is 0 then everything went find, if it's 1 then you supplied the correct arguments etc # but the request could not be completed, you'll find a human parsable reason in :statusmsg then. # # Codes 2 to 5 maps directly to UnknownRPCAction, MissingRPCData, InvalidRPCData and UnknownRPCError # see below for a description of those, in each case :statusmsg would be the reason for failure. # # To get access to the full result of the MCollective::Client#req calls you can pass in a block: # # rpc.echo(:msg => "hello world") do |resp| # pp resp # end # # In this case resp will the result from MCollective::Client#req. Instead of returning simple # text and codes as above you'll also need to handle the following exceptions: # # UnknownRPCAction - There is no matching action on the agent # MissingRPCData - You did not supply all the needed parameters for the action # InvalidRPCData - The data you did supply did not pass validation # UnknownRPCError - Some other error prevented the agent from running # # During calls a progress indicator will be shown of how many results we've received against # how many nodes were discovered, you can disable this by setting progress to false: # # rpc.progress = false # # This supports a 2nd mode where it will send the SimpleRPC request and never handle the # responses. It's a bit like UDP, it sends the request with the filter attached and you # only get back the requestid, you have no indication about results. # # You can invoke this using: # # puts rpc.echo(:process_results => false) # # This will output just the request id. # # Batched processing is supported: # # printrpc rpc.ping(:batch_size => 5) # # This will do everything exactly as normal but communicate to only 5 # agents at a time def method_missing(method_name, *args, &block) # set args to an empty hash if nothings given args = args[0] args = {} if args.nil? action = method_name.to_s @stats.reset @ddl.validate_request(action, args) if @ddl # if a global batch size is set just use that else set it # in the case that it was passed as an argument batch_mode = args.include?(:batch_size) || @batch_mode batch_size = args.delete(:batch_size) || @batch_size batch_sleep_time = args.delete(:batch_sleep_time) || @batch_sleep_time # if we were given a batch_size argument thats 0 and batch_mode was # determined to be on via global options etc this will allow a batch_size # of 0 to disable or batch_mode for this call only batch_mode = (batch_mode && Integer(batch_size) > 0) # Handle single target requests by doing discovery and picking # a random node. Then do a custom request specifying a filter # that will only match the one node. if @limit_targets target_nodes = pick_nodes_from_discovered(@limit_targets) Log.debug("Picked #{target_nodes.join(',')} as limited target(s)") custom_request(action, args, target_nodes, {"identity" => /^(#{target_nodes.join('|')})$/}, &block) elsif batch_mode call_agent_batched(action, args, options, batch_size, batch_sleep_time, &block) else call_agent(action, args, options, :auto, &block) end end # Constructs custom requests with custom filters and discovery data # the idea is that this would be used in web applications where you # might be using a cached copy of data provided by a registration agent # to figure out on your own what nodes will be responding and what your # filter would be. # # This will help you essentially short circuit the traditional cycle of: # # mc discover / call / wait for discovered nodes # # by doing discovery however you like, contructing a filter and a list of # nodes you expect responses from. # # Other than that it will work exactly like a normal call, blocks will behave # the same way, stats will be handled the same way etcetc # # If you just wanted to contact one machine for example with a client that # already has other filter options setup you can do: # # puppet.custom_request("runonce", {}, ["your.box.com"], {:identity => "your.box.com"}) # # This will do runonce action on just 'your.box.com', no discovery will be # done and after receiving just one response it will stop waiting for responses # # If direct_addressing is enabled in the config file you can provide an empty # hash as a filter, this will force that request to be a directly addressed # request which technically does not need filters. If you try to use this # mode with direct addressing disabled an exception will be raise def custom_request(action, args, expected_agents, filter = {}, &block) @ddl.validate_request(action, args) if @ddl if filter == {} && !Config.instance.direct_addressing raise "Attempted to do a filterless custom_request without direct_addressing enabled, preventing unexpected call to all nodes" end @stats.reset custom_filter = Util.empty_filter custom_options = options.clone # merge the supplied filter with the standard empty one # we could just use the merge method but I want to be sure # we dont merge in stuff that isnt actually valid ["identity", "fact", "agent", "cf_class", "compound"].each do |ftype| if filter.include?(ftype) custom_filter[ftype] = [filter[ftype], custom_filter[ftype]].flatten end end # ensure that all filters at least restrict the call to the agent we're a proxy for custom_filter["agent"] << @agent unless custom_filter["agent"].include?(@agent) custom_options[:filter] = custom_filter # Fake out the stats discovery would have put there @stats.discovered_agents([expected_agents].flatten) # Handle fire and forget requests # # If a specific reply-to was set then from the client perspective this should # be a fire and forget request too since no response will ever reach us - it # will go to the reply-to destination if args[:process_results] == false || @reply_to return fire_and_forget_request(action, args, custom_filter) end # Now do a call pretty much exactly like in method_missing except with our own # options and discovery magic if block_given? call_agent(action, args, custom_options, [expected_agents].flatten) do |r| block.call(r) end else call_agent(action, args, custom_options, [expected_agents].flatten) end end # Sets the class filter def class_filter(klass) @filter["cf_class"] << klass @filter["cf_class"].compact! reset end # Sets the fact filter def fact_filter(fact, value=nil, operator="=") return if fact.nil? return if fact == false if value.nil? parsed = Util.parse_fact_string(fact) @filter["fact"] << parsed unless parsed == false else parsed = Util.parse_fact_string("#{fact}#{operator}#{value}") @filter["fact"] << parsed unless parsed == false end @filter["fact"].compact! reset end # Sets the agent filter def agent_filter(agent) @filter["agent"] << agent @filter["agent"].compact! reset end # Sets the identity filter def identity_filter(identity) @filter["identity"] << identity @filter["identity"].compact! reset end # Set a compound filter def compound_filter(filter) @filter["compound"] << Matcher::Parser.new(filter).execution_stack reset end # Resets various internal parts of the class, most importantly it clears # out the cached discovery def reset @discovered_agents = nil end # Reet the filter to an empty one def reset_filter @filter = Util.empty_filter agent_filter @agent end # Does discovery based on the filters set, if a discovery was # previously done return that else do a new discovery. # # Alternatively if identity filters are given and none of them are # regular expressions then just use the provided data as discovered # data, avoiding discovery # # Discovery can be forced if direct_addressing is enabled by passing # in an array of nodes with :nodes or JSON data like those produced # by mcollective RPC JSON output using :json # # Will show a message indicating its doing discovery if running # verbose or if the :verbose flag is passed in. # # Use reset to force a new discovery def discover(flags={}) flags.keys.each do |key| raise "Unknown option #{key} passed to discover" unless [:verbose, :hosts, :nodes, :json].include?(key) end flags.include?(:verbose) ? verbose = flags[:verbose] : verbose = @verbose verbose = false unless @output_format == :console # flags[:nodes] and flags[:hosts] are the same thing, we should never have # allowed :hosts as that was inconsistent with the established terminology flags[:nodes] = flags.delete(:hosts) if flags.include?(:hosts) reset if flags[:nodes] || flags[:json] unless @discovered_agents # if either hosts or JSON is supplied try to figure out discovery data from there # if direct_addressing is not enabled this is a critical error as the user might # not have supplied filters so raise an exception if flags[:nodes] || flags[:json] raise "Can only supply discovery data if direct_addressing is enabled" unless Config.instance.direct_addressing hosts = [] if flags[:nodes] hosts = Helpers.extract_hosts_from_array(flags[:nodes]) elsif flags[:json] hosts = Helpers.extract_hosts_from_json(flags[:json]) end raise "Could not find any hosts in discovery data provided" if hosts.empty? @discovered_agents = hosts @force_direct_request = true # if an identity filter is supplied and it is all strings no regex we can use that # as discovery data, technically the identity filter is then redundant if we are # in direct addressing mode and we could empty it out but this use case should # only really be for a few -I's on the CLI # # For safety we leave the filter in place for now, that way we can support this # enhancement also in broadcast mode elsif options[:filter]["identity"].size > 0 regex_filters = options[:filter]["identity"].select{|i| i.match("^\/")}.size if regex_filters == 0 @discovered_agents = options[:filter]["identity"].clone @force_direct_request = true if Config.instance.direct_addressing end end end # All else fails we do it the hard way using a traditional broadcast unless @discovered_agents @stats.time_discovery :start @stderr.print("Determining the amount of hosts matching filter for #{discovery_timeout} seconds .... ") if verbose # if the requested limit is a pure number and not a percent # and if we're configured to use the first found hosts as the # limit method then pass in the limit thus minimizing the amount # of work we do in the discover phase and speeding it up significantly if @limit_method == :first and @limit_targets.is_a?(Fixnum) @discovered_agents = @client.discover(@filter, @discovery_timeout, @limit_targets) else @discovered_agents = @client.discover(@filter, @discovery_timeout) end @force_direct_request = false @stderr.puts(@discovered_agents.size) if verbose @stats.time_discovery :end end @stats.discovered_agents(@discovered_agents) RPC.discovered(@discovered_agents) @discovered_agents end # Provides a normal options hash like you would get from # Optionparser def options {:disctimeout => @discovery_timeout, :timeout => @timeout, :verbose => @verbose, :filter => @filter, :collective => @collective, :output_format => @output_format, :ttl => @ttl, :config => @config} end # Sets the collective we are communicating with def collective=(c) @collective = c @client.options[:collective] = c end # Sets and sanity checks the limit_targets variable # used to restrict how many nodes we'll target def limit_targets=(limit) if limit.is_a?(String) raise "Invalid limit specified: #{limit} valid limits are /^\d+%*$/" unless limit =~ /^\d+%*$/ begin @limit_targets = Integer(limit) rescue @limit_targets = limit end else @limit_targets = Integer(limit) end end # Sets and sanity check the limit_method variable # used to determine how to limit targets if limit_targets is set def limit_method=(method) method = method.to_sym unless method.is_a?(Symbol) raise "Unknown limit method #{method} must be :random or :first" unless [:random, :first].include?(method) @limit_method = method end # Sets the batch size, if the size is set to 0 that will disable batch mode def batch_size=(limit) raise "Can only set batch size if direct addressing is supported" unless Config.instance.direct_addressing @batch_size = Integer(limit) @batch_mode = @batch_size > 0 end def batch_sleep_time=(time) raise "Can only set batch sleep time if direct addressing is supported" unless Config.instance.direct_addressing @batch_sleep_time = Float(time) end private # Pick a number of nodes from the discovered nodes # # The count should be a string that can be either # just a number or a percentage like 10% # # It will select nodes from the discovered list based # on the rpclimitmethod configuration option which can # be either :first or anything else # # - :first would be a simple way to do a distance based # selection # - anything else will just pick one at random def pick_nodes_from_discovered(count) if count =~ /%$/ pct = (discover.size * (count.to_f / 100)).to_i pct == 0 ? count = 1 : count = pct else count = count.to_i end return discover if discover.size <= count result = [] if @limit_method == :first return discover[0, count] else count.times do rnd = rand(discover.size) result << discover[rnd] discover.delete_at(rnd) end end [result].flatten end # for requests that do not care for results just # return the request id and don't do any of the # response processing. # # We send the :process_results flag with to the # nodes so they can make decisions based on that. # # Should only be called via method_missing def fire_and_forget_request(action, args, filter=nil) @ddl.validate_request(action, args) if @ddl req = new_request(action.to_s, args) filter = options[:filter] unless filter message = Message.new(req, nil, {:agent => @agent, :type => :request, :collective => @collective, :filter => filter, :options => options}) message.reply_to = @reply_to if @reply_to return @client.sendreq(message, nil) end # Calls an agent in a way very similar to call_agent but it supports batching # the queries to the network. # # The result sets, stats, block handling etc is all exactly like you would expect # from normal call_agent. # # This is used by method_missing and works only with direct addressing mode def call_agent_batched(action, args, opts, batch_size, sleep_time, &block) raise "Batched requests requires direct addressing" unless Config.instance.direct_addressing raise "Cannot bypass result processing for batched requests" if args[:process_results] == false batch_size = Integer(batch_size) sleep_time = Float(sleep_time) Log.debug("Calling #{agent}##{action} in batches of #{batch_size} with sleep time of #{sleep_time}") @force_direct_request = true discovered = discover result = [] respcount = 0 if discovered.size > 0 req = new_request(action.to_s, args) if @progress && !block_given? twirl = Progress.new @stdout.puts @stdout.print twirl.twirl(respcount, discovered.size) end discovered.in_groups_of(batch_size) do |hosts, last_batch| message = Message.new(req, nil, {:agent => @agent, :type => :direct_request, :collective => @collective, :filter => opts[:filter], :options => opts}) message.discovered_hosts = hosts.clone.compact @client.req(message) do |resp| respcount += 1 if block_given? process_results_with_block(action, resp, block) else @stdout.print twirl.twirl(respcount, discovered.size) if @progress result << process_results_without_block(resp, action) end end @stats.noresponsefrom.concat @client.stats[:noresponsefrom] @stats.responses += @client.stats[:responses] @stats.blocktime += @client.stats[:blocktime] + sleep_time @stats.totaltime += @client.stats[:totaltime] @stats.discoverytime += @client.stats[:discoverytime] sleep sleep_time unless last_batch end else @stderr.print("\nNo request sent, we did not discover any nodes.") end @stats.finish_request RPC.stats(@stats) @stdout.print("\n") if @progress if block_given? return stats else return [result].flatten end end # Handles traditional calls to the remote agents with full stats # blocks, non blocks and everything else supported. # # Other methods of calling the nodes can reuse this code by # for example specifying custom options and discovery data def call_agent(action, args, opts, disc=:auto, &block) # Handle fire and forget requests and make sure # the :process_results value is set appropriately # # specific reply-to requests should be treated like # fire and forget since the client will never get # the responses if args[:process_results] == false || @reply_to return fire_and_forget_request(action, args) else args[:process_results] = true end # Do discovery when no specific discovery array is given # # If an array is given set the force_direct_request hint that # will tell the message object to be a direct request one if disc == :auto discovered = discover else @force_direct_request = true if Config.instance.direct_addressing discovered = disc end req = new_request(action.to_s, args) message = Message.new(req, nil, {:agent => @agent, :type => :request, :collective => @collective, :filter => opts[:filter], :options => opts}) message.discovered_hosts = discovered.clone message.type = :direct_request if @force_direct_request result = [] respcount = 0 if discovered.size > 0 if @progress && !block_given? twirl = Progress.new @stdout.puts @stdout.print twirl.twirl(respcount, discovered.size) end @client.req(message) do |resp| respcount += 1 if block_given? process_results_with_block(action, resp, block) else @stdout.print twirl.twirl(respcount, discovered.size) if @progress result << process_results_without_block(resp, action) end end @stats.client_stats = @client.stats else @stderr.print("\nNo request sent, we did not discover any nodes.") end @stats.finish_request RPC.stats(@stats) @stdout.print("\n\n") if @progress if block_given? return stats else return [result].flatten end end # Handles result sets that has no block associated, sets fails and ok # in the stats object and return a hash of the response to send to the # caller def process_results_without_block(resp, action) @stats.node_responded(resp[:senderid]) if resp[:body][:statuscode] == 0 || resp[:body][:statuscode] == 1 @stats.ok if resp[:body][:statuscode] == 0 @stats.fail if resp[:body][:statuscode] == 1 return Result.new(@agent, action, {:sender => resp[:senderid], :statuscode => resp[:body][:statuscode], :statusmsg => resp[:body][:statusmsg], :data => resp[:body][:data]}) else @stats.fail return Result.new(@agent, action, {:sender => resp[:senderid], :statuscode => resp[:body][:statuscode], :statusmsg => resp[:body][:statusmsg], :data => nil}) end end # process client requests by calling a block on each result # in this mode we do not do anything fancy with the result # objects and we raise exceptions if there are problems with # the data def process_results_with_block(action, resp, block) @stats.node_responded(resp[:senderid]) if resp[:body][:statuscode] == 0 || resp[:body][:statuscode] == 1 @stats.time_block_execution :start case block.arity when 1 block.call(resp) when 2 rpcresp = Result.new(@agent, action, {:sender => resp[:senderid], :statuscode => resp[:body][:statuscode], :statusmsg => resp[:body][:statusmsg], :data => resp[:body][:data]}) block.call(resp, rpcresp) end @stats.time_block_execution :end else case resp[:body][:statuscode] when 2 raise UnknownRPCAction, resp[:body][:statusmsg] when 3 raise MissingRPCData, resp[:body][:statusmsg] when 4 raise InvalidRPCData, resp[:body][:statusmsg] when 5 raise UnknownRPCError, resp[:body][:statusmsg] end end end end end end mcollective-2.0.0/lib/mcollective/rpc/ddl.rb0000644000175000017500000002316711747546503020002 0ustar jonasjonasmodule MCollective module RPC # A class that helps creating data description language files # for agents. You can define meta data, actions, input and output # describing the behavior of your agent. # # Later you can access this information to assist with creating # of user interfaces or online help # # A sample DDL can be seen below, you'd put this in your agent # dir as .ddl # # metadata :name => "SimpleRPC Service Agent", # :description => "Agent to manage services using the Puppet service provider", # :author => "R.I.Pienaar", # :license => "GPLv2", # :version => "1.1", # :url => "http://mcollective-plugins.googlecode.com/", # :timeout => 60 # # action "status", :description => "Gets the status of a service" do # display :always # # input "service", # :prompt => "Service Name", # :description => "The service to get the status for", # :type => :string, # :validation => '^[a-zA-Z\-_\d]+$', # :optional => true, # :maxlength => 30 # # output "status", # :description => "The status of service", # :display_as => "Service Status" # end class DDL attr_reader :meta def initialize(agent, loadddl=true) @actions = {} @meta = {} @config = MCollective::Config.instance @agent = agent if loadddl if ddlfile = findddlfile(agent) instance_eval(File.read(ddlfile)) else raise("Can't find DDL for agent '#{agent}'") end end end def findddlfile(agent) @config.libdir.each do |libdir| ddlfile = File.join([libdir, "mcollective", "agent", "#{agent}.ddl"]) if File.exist?(ddlfile) Log.debug("Found #{agent} ddl at #{ddlfile}") return ddlfile end end return false end # Registers meta data for the introspection hash def metadata(meta) [:name, :description, :author, :license, :version, :url, :timeout].each do |arg| raise "Metadata needs a :#{arg}" unless meta.include?(arg) end @meta = meta end # Creates the definition for an action, you can nest input definitions inside the # action to attach inputs and validation to the actions # # action "status", :description => "Restarts a Service" do # display :always # # input "service", # :prompt => "Service Action", # :description => "The action to perform", # :type => :list, # :optional => true, # :list => ["start", "stop", "restart", "status"] # # output "status" # :description => "The status of the service after the action" # # end def action(name, input, &block) raise "Action needs a :description" unless input.include?(:description) unless @actions.include?(name) @actions[name] = {} @actions[name][:action] = name @actions[name][:input] = {} @actions[name][:output] = {} @actions[name][:display] = :failed @actions[name][:description] = input[:description] end # if a block is passed it might be creating input methods, call it # we set @current_action so the input block can know what its talking # to, this is probably an epic hack, need to improve. @current_action = name block.call if block_given? @current_action = nil end # Registers an input argument for a given action # # See the documentation for action for how to use this def input(argument, properties) raise "Cannot figure out what action input #{argument} belongs to" unless @current_action action = @current_action [:prompt, :description, :type, :optional].each do |arg| raise "Input needs a :#{arg}" unless properties.include?(arg) end @actions[action][:input][argument] = {:prompt => properties[:prompt], :description => properties[:description], :type => properties[:type], :optional => properties[:optional]} case properties[:type] when :string raise "Input type :string needs a :validation argument" unless properties.include?(:validation) raise "Input type :string needs a :maxlength argument" unless properties.include?(:maxlength) @actions[action][:input][argument][:validation] = properties[:validation] @actions[action][:input][argument][:maxlength] = properties[:maxlength] when :list raise "Input type :list needs a :list argument" unless properties.include?(:list) @actions[action][:input][argument][:list] = properties[:list] end end # Registers an output argument for a given action # # See the documentation for action for how to use this def output(argument, properties) raise "Cannot figure out what action input #{argument} belongs to" unless @current_action raise "Output #{argument} needs a description argument" unless properties.include?(:description) raise "Output #{argument} needs a display_as argument" unless properties.include?(:display_as) action = @current_action @actions[action][:output][argument] = {:description => properties[:description], :display_as => properties[:display_as]} end # Sets the display preference to either :ok, :failed, :flatten or :always # operates on action level def display(pref) # defaults to old behavior, complain if its supplied and invalid unless [:ok, :failed, :flatten, :always].include?(pref) raise "Display preference #{pref} is not valid, should be :ok, :failed, :flatten or :always" end action = @current_action @actions[action][:display] = pref end # Generates help using the template based on the data # created with metadata and input def help(template) template = IO.read(template) meta = @meta actions = @actions erb = ERB.new(template, 0, '%') erb.result(binding) end # Returns an array of actions this agent support def actions @actions.keys end # Returns the interface for a specific action def action_interface(name) @actions[name] || {} end # Helper to use the DDL to figure out if the remote call should be # allowed based on action name and inputs. def validate_request(action, arguments) # is the action known? unless actions.include?(action) raise DDLValidationError, "Attempted to call action #{action} for #{@agent} but it's not declared in the DDL" end input = action_interface(action)[:input] input.keys.each do |key| unless input[key][:optional] unless arguments.keys.include?(key) raise DDLValidationError, "Action #{action} needs a #{key} argument" end end # validate strings, lists and booleans, we'll add more types of validators when # all the use cases are clear # # only does validation for arguments actually given, since some might # be optional. We validate the presense of the argument earlier so # this is a safe assumption, just to skip them. # # :string can have maxlength and regex. A maxlength of 0 will bypasss checks # :list has a array of valid values if arguments.keys.include?(key) case input[key][:type] when :string raise DDLValidationError, "Input #{key} should be a string" unless arguments[key].is_a?(String) if input[key][:maxlength].to_i > 0 if arguments[key].size > input[key][:maxlength].to_i raise DDLValidationError, "Input #{key} is longer than #{input[key][:maxlength]} character(s)" end end unless arguments[key].match(Regexp.new(input[key][:validation])) raise DDLValidationError, "Input #{key} does not match validation regex #{input[key][:validation]}" end when :list unless input[key][:list].include?(arguments[key]) raise DDLValidationError, "Input #{key} doesn't match list #{input[key][:list].join(', ')}" end when :boolean unless [TrueClass, FalseClass].include?(arguments[key].class) raise DDLValidationError, "Input #{key} should be a boolean" end when :integer raise DDLValidationError, "Input #{key} should be a integer" unless arguments[key].is_a?(Fixnum) when :float raise DDLValidationError, "Input #{key} should be a floating point number" unless arguments[key].is_a?(Float) when :number raise DDLValidationError, "Input #{key} should be a number" unless arguments[key].is_a?(Numeric) end end end true end end end end mcollective-2.0.0/lib/mcollective/rpc/audit.rb0000644000175000017500000000244511747546503020341 0ustar jonasjonasmodule MCollective module RPC # Auditing of requests is done only for SimpleRPC requests, you provide # a plugin in the MCollective::Audit::* namespace which the SimpleRPC # framework calls for each message # # We provide a simple one that logs to a logfile in the class # MCollective::Audit::Logfile you can create your own: # # Create a class in plugins/mcollective/audit/.rb # # You must inherit from MCollective::RPC::Audit which will take # care of registering you with the plugin system. # # Your plugin must provide audit_request(request, connection) # the request parameter will be an instance of MCollective::RPC::Request # # To enable auditing you should set: # # rpcaudit = 1 # rpcauditprovider = Logfile # # in the config file this will enable logging using the # MCollective::Audit::Logile class # # The Audit class acts as a base for audit plugins and takes care of registering them # with the plugin manager class Audit def self.inherited(klass) PluginManager << {:type => "rpcaudit_plugin", :class => klass.to_s} end def audit_request(request, connection) @log.error("audit_request is not implimented in #{this.class}") end end end end mcollective-2.0.0/lib/mcollective/matcher/0000755000175000017500000000000011747546503017540 5ustar jonasjonasmcollective-2.0.0/lib/mcollective/matcher/scanner.rb0000644000175000017500000001036211747546503021520 0ustar jonasjonasmodule MCollective module Matcher class Scanner attr_accessor :arguments, :token_index def initialize(arguments) @token_index = 0 @arguments = arguments end # Scans the input string and identifies single language tokens def get_token if @token_index >= @arguments.size return nil end begin case @arguments.split("")[@token_index] when "(" return "(", "(" when ")" return ")", ")" when "n" if (@arguments.split("")[@token_index + 1] == "o") && (@arguments.split("")[@token_index + 2] == "t") && ((@arguments.split("")[@token_index + 3] == " ") || (@arguments.split("")[@token_index + 3] == "(")) @token_index += 2 return "not", "not" else gen_statement end when "!" return "not", "not" when "a" if (@arguments.split("")[@token_index + 1] == "n") && (@arguments.split("")[@token_index + 2] == "d") && ((@arguments.split("")[@token_index + 3] == " ") || (@arguments.split("")[@token_index + 3] == "(")) @token_index += 2 return "and", "and" else gen_statement end when "o" if (@arguments.split("")[@token_index + 1] == "r") && ((@arguments.split("")[@token_index + 2] == " ") || (@arguments.split("")[@token_index + 2] == "(")) @token_index += 1 return "or", "or" else gen_statement end when " " return " ", " " else gen_statement end end rescue NoMethodError => e pp e raise "Cannot end statement with 'and', 'or', 'not'" end private # Helper generates a statement token def gen_statement current_token_value = "" j = @token_index begin if (@arguments.split("")[j] == "/") begin current_token_value << @arguments.split("")[j] j += 1 if @arguments.split("")[j] == "/" current_token_value << "/" break end end until (j >= @arguments.size) || (@arguments.split("")[j] =~ /\//) elsif (@arguments.split("")[j] =~ /=|<|>/) while !(@arguments.split("")[j] =~ /=|<|>/) current_token_value << @arguments.split("")[j] j += 1 end current_token_value << @arguments.split("")[j] j += 1 if @arguments.split("")[j] == "/" begin current_token_value << @arguments.split("")[j] j += 1 if @arguments.split("")[j] == "/" current_token_value << "/" break end end until (j >= @arguments.size) || (@arguments.split("")[j] =~ /\//) else while (j < @arguments.size) && ((@arguments.split("")[j] != " ") && (@arguments.split("")[j] != ")")) current_token_value << @arguments.split("")[j] j += 1 end end else begin current_token_value << @arguments.split("")[j] j += 1 end until (j >= @arguments.size) || (@arguments.split("")[j] =~ /\s|\)/) end rescue Exception => e raise "Invalid token found - '#{current_token_value}'" end if current_token_value =~ /^(and|or|not|!)$/ raise "Class name cannot be 'and', 'or', 'not'. Found '#{current_token_value}'" end @token_index += current_token_value.size - 1 return "statement", current_token_value end end end end mcollective-2.0.0/lib/mcollective/matcher/parser.rb0000644000175000017500000000656711747546503021377 0ustar jonasjonasmodule MCollective module Matcher class Parser attr_reader :scanner, :execution_stack def initialize(args) @scanner = Scanner.new(args) @execution_stack = [] parse end # Parse the input string, one token at a time a contruct the call stack def parse p_token,p_token_value = nil c_token,c_token_value = @scanner.get_token parenth = 0 while (c_token != nil) @scanner.token_index += 1 n_token, n_token_value = @scanner.get_token unless n_token == " " case c_token when "and" unless (n_token =~ /not|statement|\(/) || (scanner.token_index == scanner.arguments.size) raise "Error at column #{scanner.token_index}. \nExpected 'not', 'statement' or '('. Found '#{n_token_value}'" end if p_token == nil raise "Error at column #{scanner.token_index}. \n Expression cannot start with 'and'" elsif (p_token == "and" || p_token == "or") raise "Error at column #{scanner.token_index}. \n #{p_token} cannot be followed by 'and'" end when "or" unless (n_token =~ /not|statement|\(/) || (scanner.token_index == scanner.arguments.size) raise "Error at column #{scanner.token_index}. \nExpected 'not', 'statement', '('. Found '#{n_token_value}'" end if p_token == nil raise "Error at column #{scanner.token_index}. \n Expression cannot start with 'or'" elsif (p_token == "and" || p_token == "or") raise "Error at column #{scanner.token_index}. \n #{p_token} cannot be followed by 'or'" end when "not" unless n_token =~ /statement|\(|not/ raise "Error at column #{scanner.token_index}. \nExpected 'statement' or '('. Found '#{n_token_value}'" end when "statement" unless n_token =~ /and|or|\)/ unless scanner.token_index == scanner.arguments.size raise "Error at column #{scanner.token_index}. \nExpected 'and', 'or', ')'. Found '#{n_token_value}'" end end when ")" unless (n_token =~ /|and|or|not|\(/) unless(scanner.token_index == scanner.arguments.size) raise "Error at column #{scanner.token_index}. \nExpected 'and', 'or', 'not' or '('. Found '#{n_token_value}'" end end parenth += 1 when "(" unless n_token =~ /statement|not|\(/ raise "Error at column #{scanner.token_index}. \nExpected 'statement', '(', not. Found '#{n_token_value}'" end parenth -= 1 else raise "Unexpected token found at column #{scanner.token_index}. '#{c_token_value}'" end unless n_token == " " @execution_stack << {c_token => c_token_value} end p_token, p_token_value = c_token, c_token_value c_token, c_token_value = n_token, n_token_value end end if parenth < 0 raise "Error. Missing parentheses ')'." elsif parenth > 0 raise "Error. Missing parentheses '('." end end end end end mcollective-2.0.0/lib/mcollective/pluginpackager.rb0000644000175000017500000000324311747546503021440 0ustar jonasjonasmodule MCollective module PluginPackager # Plugin definition classes autoload :AgentDefinition, "mcollective/pluginpackager/agent_definition" autoload :StandardDefinition, "mcollective/pluginpackager/standard_definition" # Package implementation plugins def self.load_packagers PluginManager.find_and_load("pluginpackager") end def self.[](klass) const_get("#{klass}") end # Fetch and return metadata from plugin DDL def self.get_metadata(path, type) ddl = MCollective::RPC::DDL.new("package", false) ddl.instance_eval File.read(Dir.glob(File.join(path, type, "*.ddl")).first) ddl.meta end # Checks if a directory is present and not empty def self.check_dir_present(path) (File.directory?(path) && !Dir.glob(File.join(path, "*")).empty?) end # Quietly calls a block if verbose parameter is false def self.do_quietly?(verbose, &block) unless verbose old_stdout = $stdout.clone $stdout.reopen(File.new("/dev/null", "w")) begin block.call rescue Exception => e $stdout.reopen old_stdout raise e ensure $stdout.reopen old_stdout end else block.call end end # Checks if a build tool is present on the system def self.build_tool?(build_tool) ENV["PATH"].split(File::PATH_SEPARATOR).each do |path| builder = File.join(path, build_tool) if File.exists?(builder) return true end end false end def self.safe_system(*args) raise RuntimeError, "Failed: #{args.join(' ')}" unless system *args end end end mcollective-2.0.0/lib/mcollective/connector/0000755000175000017500000000000011747546503020107 5ustar jonasjonasmcollective-2.0.0/lib/mcollective/connector/base.rb0000644000175000017500000000245611747546503021355 0ustar jonasjonasmodule MCollective # Connector plugins handle the communications with the middleware, you can provide your own to speak # to something other than Stomp, your plugins must inherit from MCollective::Connector::Base and should # provide the following methods: # # connect - Creates a connection to the middleware, no arguments should get its parameters from the config # receive - Receive data from the middleware, should act like a blocking call only returning if/when data # was received. It should get data from all subscribed channels/topics. Individual messages # should be returned as MCollective::Request objects with the payload provided # publish - Takes a target and msg, should send the message to the supplied target topic or destination # subscribe - Adds a subscription to a specific message source # unsubscribe - Removes a subscription to a specific message source # disconnect - Disconnects from the middleware # # These methods are all that's needed for a new connector protocol and should hopefully be simple # enough to not have tied us to Stomp. module Connector class Base def self.inherited(klass) PluginManager << {:type => "connector_plugin", :class => klass.to_s} end end end end mcollective-2.0.0/lib/mcollective/log.rb0000644000175000017500000000466711747546503017240 0ustar jonasjonasmodule MCollective # A simple class that allows logging at various levels. class Log class << self @logger = nil # Obtain the class name of the currently configured logger def logger @logger.class end # Logs at info level def info(msg) log(:info, msg) end # Logs at warn level def warn(msg) log(:warn, msg) end # Logs at debug level def debug(msg) log(:debug, msg) end # Logs at fatal level def fatal(msg) log(:fatal, msg) end # Logs at error level def error(msg) log(:error, msg) end # handle old code that relied on this class being a singleton def instance self end # increments the active log level def cycle_level @logger.cycle_level if @configured end # logs a message at a certain level def log(level, msg) configure unless @configured raise "Unknown log level" unless [:error, :fatal, :debug, :warn, :info].include?(level) if @logger @logger.log(level, from, msg) else t = Time.new.strftime("%H:%M:%S") STDERR.puts "#{t}: #{level}: #{from}: #{msg}" end end # sets the logger class to use def set_logger(logger) @logger = logger end # configures the logger class, if the config has not yet been loaded # we default to the console logging class and do not set @configured # so that future calls to the log method will keep attempting to configure # the logger till we eventually get a logging preference from the config # module def configure(logger=nil) unless logger logger_type = "console" config = Config.instance if config.configured logger_type = config.logger_type @configured = true end require "mcollective/logger/#{logger_type.downcase}_logger" set_logger(eval("MCollective::Logger::#{logger_type.capitalize}_logger.new")) else set_logger(logger) @configured = true end @logger.start rescue Exception => e @configured = false STDERR.puts "Could not start logger: #{e.class} #{e}" end # figures out the filename that called us def from from = File.basename(caller[2]) end end end end mcollective-2.0.0/lib/mcollective/applications.rb0000644000175000017500000001044211747546503021131 0ustar jonasjonasmodule MCollective class Applications def self.[](appname) load_application(appname) PluginManager["#{appname}_application"] end def self.run(appname) load_config begin load_application(appname) rescue Exception => e e.backtrace.first << RPC::Helpers.colorize(:red, " <----") STDERR.puts "Application '#{appname}' failed to load:" STDERR.puts STDERR.puts RPC::Helpers.colorize(:red, " #{e} (#{e.class})") STDERR.puts STDERR.puts " %s" % [e.backtrace.join("\n ")] exit 1 end PluginManager["#{appname}_application"].run end def self.load_application(appname) return if PluginManager.include?("#{appname}_application") load_config PluginManager.loadclass "MCollective::Application::#{appname.capitalize}" PluginManager << {:type => "#{appname}_application", :class => "MCollective::Application::#{appname.capitalize}"} end # Returns an array of applications found in the lib dirs def self.list load_config applist = [] Config.instance.libdir.each do |libdir| applicationdir = "#{libdir}/mcollective/application" next unless File.directory?(applicationdir) Dir.entries(applicationdir).grep(/\.rb$/).each do |application| applist << File.basename(application, ".rb") end end applist rescue SystemExit exit 1 rescue Exception => e STDERR.puts "Failed to generate application list: #{e.class}: #{e}" exit 1 end # Filters a string of opts out using Shellwords # keeping only things related to --config and -c def self.filter_extra_options(opts) res = "" words = Shellwords.shellwords(opts) words.each_with_index do |word,idx| if word == "-c" return "--config=#{words[idx + 1]}" elsif word == "--config" return "--config=#{words[idx + 1]}" elsif word =~ /\-c=/ return word elsif word =~ /\-\-config=/ return word end end return "" end # We need to know the config file in order to know the libdir # so that we can find applications. # # The problem is the CLI might be stuffed with options only the # app in the libdir might understand so we have a chicken and # egg situation. # # We're parsing and filtering MCOLLECTIVE_EXTRA_OPTS removing # all but config related options and parsing the options looking # just for the config file. # # We're handling failures gracefully and finally restoring the # ARG and MCOLLECTIVE_EXTRA_OPTS to the state they were before # we started parsing. # # This is mostly a hack, when we're redoing how config works # this stuff should be made less sucky def self.load_config return if Config.instance.configured original_argv = ARGV.clone original_extra_opts = ENV["MCOLLECTIVE_EXTRA_OPTS"].clone rescue nil configfile = nil parser = OptionParser.new parser.on("--config CONFIG", "-c", "Config file") do |f| configfile = f end parser.program_name = $0 parser.on("--help") # avoid option parsers own internal version handling that sux parser.on("-v", "--verbose") if original_extra_opts begin # optparse will parse the whole ENV in one go and refuse # to play along with the retry trick I do below so in # order to handle unknown options properly I parse out # only -c and --config deleting everything else and # then restore the environment variable later when I # am done with it ENV["MCOLLECTIVE_EXTRA_OPTS"] = filter_extra_options(ENV["MCOLLECTIVE_EXTRA_OPTS"].clone) parser.environment("MCOLLECTIVE_EXTRA_OPTS") rescue Exception => e Log.error("Failed to parse MCOLLECTIVE_EXTRA_OPTS: #{e}") end ENV["MCOLLECTIVE_EXTRA_OPTS"] = original_extra_opts.clone end begin parser.parse! rescue OptionParser::InvalidOption => e retry end ARGV.clear original_argv.each {|a| ARGV << a} configfile = Util.config_file_for_user unless configfile Config.instance.loadconfig(configfile) end end end mcollective-2.0.0/lib/mcollective/facts.rb0000644000175000017500000000211611747546503017542 0ustar jonasjonasmodule MCollective # This is a class that gives access to the configured fact provider # such as MCollectives::Facts::Facter that uses Reductive Labs facter # # The actual provider is pluggable and configurable using the 'factsource' # configuration option. # # To develop a new factsource simply create a class under MCollective::Facts:: # and provide the following classes: # # self.get_fact(fact) # self.has_fact?(fact) # # You can also just inherit from MCollective::Facts::Base and provide just the # # self.get_facts # # method that should return a hash of facts. module Facts autoload :Base, "mcollective/facts/base" @@config = nil # True if we know of a specific fact else false def self.has_fact?(fact, value) PluginManager["facts_plugin"].get_fact(fact) == value ? true : false end # Get the value of a fact def self.get_fact(fact) PluginManager["facts_plugin"].get_fact(fact) end # Get the value of a fact def self.[](fact) PluginManager["facts_plugin"].get_fact(fact) end end end mcollective-2.0.0/lib/mcollective/vendor.rb0000644000175000017500000000230011747546503017732 0ustar jonasjonasmodule MCollective # Simple module to manage vendored code. # # To vendor a library simply download its whole git repo or untar # into vendor/libraryname and create a load_libraryname.rb file # to add its libdir into the $:. # # Once you have that file, add a require line in vendor/require_vendored.rb # which will run after all the load_* files. # # The intention is to not change vendored libraries and to eventually # make adding them in optional so that distros can simply adjust their # packaging to exclude this directory and the various load_xxx.rb scripts # if they wish to install these gems as native packages. class Vendor class << self def vendor_dir File.join([File.dirname(File.expand_path(__FILE__)), "vendor"]) end def load_entry(entry) Log.debug("Loading vendored #{$1}") load "#{vendor_dir}/#{entry}" end def require_libs require 'mcollective/vendor/require_vendored' end def load_vendored Dir.entries(vendor_dir).each do |entry| if entry.match(/load_(\w+?)\.rb$/) load_entry entry end end require_libs end end end end mcollective-2.0.0/lib/mcollective/shell.rb0000644000175000017500000000532511747546503017556 0ustar jonasjonasmodule MCollective # Wrapper around systemu that handles executing of system commands # in a way that makes stdout, stderr and status available. Supports # timeouts and sets a default sane environment. # # s = Shell.new("date", opts) # s.runcommand # puts s.stdout # puts s.stderr # puts s.status.exitcode # # Options hash can have: # # cwd - the working directory the command will be run from # stdin - a string that will be sent to stdin of the program # stdout - a variable that will receive stdout, must support << # stderr - a variable that will receive stdin, must support << # environment - the shell environment, defaults to include LC_ALL=C # set to nil to clear the environment even of LC_ALL # class Shell attr_reader :environment, :command, :status, :stdout, :stderr, :stdin, :cwd def initialize(command, options={}) @environment = {"LC_ALL" => "C"} @command = command @status = nil @stdout = "" @stderr = "" @stdin = nil @cwd = Dir.tmpdir options.each do |opt, val| case opt.to_s when "stdout" raise "stdout should support <<" unless val.respond_to?("<<") @stdout = val when "stderr" raise "stderr should support <<" unless val.respond_to?("<<") @stderr = val when "stdin" raise "stdin should be a String" unless val.is_a?(String) @stdin = val when "cwd" raise "Directory #{val} does not exist" unless File.directory?(val) @cwd = val when "environment" if val.nil? @environment = {} else @environment.merge!(val.dup) end end end end # Actually does the systemu call passing in the correct environment, stdout and stderr def runcommand opts = {"env" => @environment, "stdout" => @stdout, "stderr" => @stderr, "cwd" => @cwd} opts["stdin"] = @stdin if @stdin # Running waitpid on the cid here will start a thread # with the waitpid in it, this way even if the thread # that started this process gets killed due to agent # timeout or such there will still be a waitpid waiting # for the child to exit and not leave zombies. @status = systemu(@command, opts) do |cid| begin sleep 1 Process::waitpid(cid) rescue SystemExit rescue Errno::ECHILD rescue Exception => e Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}") end end end end end mcollective-2.0.0/lib/mcollective/vendor/0000755000175000017500000000000011751200300017363 5ustar jonasjonasmcollective-2.0.0/lib/mcollective/vendor/require_vendored.rb0000644000175000017500000000004111747546503023274 0ustar jonasjonasrequire 'systemu' require 'json' mcollective-2.0.0/lib/mcollective/client.rb0000644000175000017500000002201311747546503017716 0ustar jonasjonasmodule MCollective # Helpers for writing clients that can talk to agents, do discovery and so forth class Client attr_accessor :options, :stats def initialize(configfile) @config = Config.instance @config.loadconfig(configfile) unless @config.configured @connection = PluginManager["connector_plugin"] @security = PluginManager["security_plugin"] @security.initiated_by = :client @options = nil @subscriptions = {} @connection.connect end # Returns the configured main collective if no # specific collective is specified as options def collective if @options[:collective].nil? @config.main_collective else @options[:collective] end end # Disconnects cleanly from the middleware def disconnect Log.debug("Disconnecting from the middleware") @connection.disconnect end # Sends a request and returns the generated request id, doesn't wait for # responses and doesn't execute any passed in code blocks for responses def sendreq(msg, agent, filter = {}) if msg.is_a?(Message) request = msg agent = request.agent else ttl = @options[:ttl] || @config.ttl request = Message.new(msg, nil, {:agent => agent, :type => :request, :collective => collective, :filter => filter, :ttl => ttl}) request.reply_to = @options[:reply_to] if @options[:reply_to] end request.encode! Log.debug("Sending request #{request.requestid} to the #{request.agent} agent with ttl #{request.ttl} in collective #{request.collective}") subscribe(agent, :reply) request.publish request.requestid end def subscribe(agent, type) unless @subscriptions.include?(agent) subscription = Util.make_subscriptions(agent, type, collective) Log.debug("Subscribing to #{type} target for agent #{agent}") Util.subscribe(subscription) @subscriptions[agent] = 1 end end def unsubscribe(agent, type) if @subscriptions.include?(agent) subscription = Util.make_subscriptions(agent, type, collective) Log.debug("Unsubscribing #{type} target for #{agent}") Util.unsubscribe(subscription) @subscriptions.delete(agent) end end # Blocking call that waits for ever for a message to arrive. # # If you give it a requestid this means you've previously send a request # with that ID and now you just want replies that matches that id, in that # case the current connection will just ignore all messages not directed at it # and keep waiting for more till it finds a matching message. def receive(requestid = nil) reply = nil begin reply = @connection.receive reply.type = :reply reply.expected_msgid = requestid reply.decode! reply.payload[:senderid] = Digest::MD5.hexdigest(reply.payload[:senderid]) if ENV.include?("MCOLLECTIVE_ANON") raise(MsgDoesNotMatchRequestID, "Message reqid #{requestid} does not match our reqid #{reply.requestid}") unless reply.requestid == requestid rescue SecurityValidationFailed => e Log.warn("Ignoring a message that did not pass security validations") retry rescue MsgDoesNotMatchRequestID => e Log.debug("Ignoring a message for some other client") retry end reply end # Performs a discovery of nodes matching the filter passed # returns an array of nodes # # An integer limit can be supplied this will have the effect # of the discovery being cancelled soon as it reached the # requested limit of hosts def discover(filter, timeout, limit=0) raise "Limit has to be an integer" unless limit.is_a?(Fixnum) begin hosts = [] Timeout.timeout(timeout) do reqid = sendreq("ping", "discovery", filter) Log.debug("Waiting #{timeout} seconds for discovery replies to request #{reqid}") loop do reply = receive(reqid) Log.debug("Got discovery reply from #{reply.payload[:senderid]}") hosts << reply.payload[:senderid] return hosts if limit > 0 && hosts.size == limit end end rescue Timeout::Error => e rescue Exception => e raise ensure unsubscribe("discovery", :reply) end hosts.sort end # Send a request, performs the passed block for each response # # times = req("status", "mcollectived", options, client) {|resp| # pp resp # } # # It returns a hash of times and timeouts for discovery and total run is taken from the options # hash which in turn is generally built using MCollective::Optionparser def req(body, agent=nil, options=false, waitfor=0) if body.is_a?(Message) agent = body.agent options = body.options waitfor = body.discovered_hosts.size || 0 end stat = {:starttime => Time.now.to_f, :discoverytime => 0, :blocktime => 0, :totaltime => 0} options = @options unless options STDOUT.sync = true hosts_responded = 0 begin Timeout.timeout(options[:timeout]) do reqid = sendreq(body, agent, options[:filter]) loop do resp = receive(reqid) hosts_responded += 1 yield(resp.payload) break if (waitfor != 0 && hosts_responded >= waitfor) end end rescue Interrupt => e rescue Timeout::Error => e ensure unsubscribe(agent, :reply) end stat[:totaltime] = Time.now.to_f - stat[:starttime] stat[:blocktime] = stat[:totaltime] - stat[:discoverytime] stat[:responses] = hosts_responded stat[:noresponsefrom] = [] @stats = stat return stat end # Performs a discovery and then send a request, performs the passed block for each response # # times = discovered_req("status", "mcollectived", options, client) {|resp| # pp resp # } # # It returns a hash of times and timeouts for discovery and total run is taken from the options # hash which in turn is generally built using MCollective::Optionparser def discovered_req(body, agent, options=false) stat = {:starttime => Time.now.to_f, :discoverytime => 0, :blocktime => 0, :totaltime => 0} options = @options unless options STDOUT.sync = true print("Determining the amount of hosts matching filter for #{options[:disctimeout]} seconds .... ") begin discovered_hosts = discover(options[:filter], options[:disctimeout]) discovered = discovered_hosts.size hosts_responded = [] hosts_not_responded = discovered_hosts stat[:discoverytime] = Time.now.to_f - stat[:starttime] puts("#{discovered}\n\n") rescue Interrupt puts("Discovery interrupted.") exit! end raise("No matching clients found") if discovered == 0 begin Timeout.timeout(options[:timeout]) do reqid = sendreq(body, agent, options[:filter]) (1..discovered).each do |c| resp = receive(reqid) hosts_responded << resp.payload[:senderid] hosts_not_responded.delete(resp.payload[:senderid]) if hosts_not_responded.include?(resp.payload[:senderid]) yield(resp.payload) end end rescue Interrupt => e rescue Timeout::Error => e end stat[:totaltime] = Time.now.to_f - stat[:starttime] stat[:blocktime] = stat[:totaltime] - stat[:discoverytime] stat[:responses] = hosts_responded.size stat[:responsesfrom] = hosts_responded stat[:noresponsefrom] = hosts_not_responded stat[:discovered] = discovered @stats = stat return stat end # Prints out the stats returns from req and discovered_req in a nice way def display_stats(stats, options=false, caption="stomp call summary") options = @options unless options if options[:verbose] puts("\n---- #{caption} ----") if stats[:discovered] puts(" Nodes: #{stats[:discovered]} / #{stats[:responses]}") else puts(" Nodes: #{stats[:responses]}") end printf(" Start Time: %s\n", Time.at(stats[:starttime])) printf(" Discovery Time: %.2fms\n", stats[:discoverytime] * 1000) printf(" Agent Time: %.2fms\n", stats[:blocktime] * 1000) printf(" Total Time: %.2fms\n", stats[:totaltime] * 1000) else if stats[:discovered] printf("\nFinished processing %d / %d hosts in %.2f ms\n\n", stats[:responses], stats[:discovered], stats[:blocktime] * 1000) else printf("\nFinished processing %d hosts in %.2f ms\n\n", stats[:responses], stats[:blocktime] * 1000) end end if stats[:noresponsefrom].size > 0 puts("\nNo response from:\n") stats[:noresponsefrom].each do |c| puts if c % 4 == 1 printf("%30s", c) end puts end end end end mcollective-2.0.0/lib/mcollective/agents.rb0000644000175000017500000001141611747546503017726 0ustar jonasjonasmodule MCollective # A collection of agents, loads them, reloads them and dispatches messages to them. # It uses the PluginManager to store, load and manage instances of plugins. class Agents def initialize(agents = {}) @config = Config.instance raise ("Configuration has not been loaded, can't load agents") unless @config.configured @@agents = agents loadagents end # Deletes all agents def clear! @@agents.each_key do |agent| PluginManager.delete "#{agent}_agent" Util.unsubscribe(Util.make_subscriptions(agent, :broadcast)) end @@agents = {} end # Loads all agents from disk def loadagents Log.debug("Reloading all agents from disk") clear! @config.libdir.each do |libdir| agentdir = "#{libdir}/mcollective/agent" next unless File.directory?(agentdir) Dir.new(agentdir).grep(/\.rb$/).each do |agent| agentname = File.basename(agent, ".rb") loadagent(agentname) unless PluginManager.include?("#{agentname}_agent") end end end # Loads a specified agent from disk if available def loadagent(agentname) agentfile = findagentfile(agentname) return false unless agentfile classname = class_for_agent(agentname) PluginManager.delete("#{agentname}_agent") begin single_instance = ["registration", "discovery"].include?(agentname) PluginManager.loadclass(classname) if activate_agent?(agentname) PluginManager << {:type => "#{agentname}_agent", :class => classname, :single_instance => single_instance} Util.subscribe(Util.make_subscriptions(agentname, :broadcast)) unless @@agents.include?(agentname) @@agents[agentname] = {:file => agentfile} return true else Log.debug("Not activating agent #{agentname} due to agent policy in activate? method") return false end rescue Exception => e Log.error("Loading agent #{agentname} failed: #{e}") PluginManager.delete("#{agentname}_agent") return false end end # Builds a class name string given a Agent name def class_for_agent(agent) "MCollective::Agent::#{agent.capitalize}" end # Checks if a plugin should be activated by # calling #activate? on it if it responds to # that method else always activate it def activate_agent?(agent) klass = Kernel.const_get("MCollective").const_get("Agent").const_get(agent.capitalize) if klass.respond_to?("activate?") return klass.activate? else Log.debug("#{klass} does not have an activate? method, activating as default") return true end rescue Exception => e Log.warn("Agent activation check for #{agent} failed: #{e.class}: #{e}") return false end # searches the libdirs for agents def findagentfile(agentname) @config.libdir.each do |libdir| agentfile = File.join([libdir, "mcollective", "agent", "#{agentname}.rb"]) if File.exist?(agentfile) Log.debug("Found #{agentname} at #{agentfile}") return agentfile end end return false end # Determines if we have an agent with a certain name def include?(agentname) PluginManager.include?("#{agentname}_agent") end # Returns the help for an agent after first trying to get # rid of some indentation infront def help(agentname) raise("No such agent") unless include?(agentname) body = PluginManager["#{agentname}_agent"].help.split("\n") if body.first =~ /^(\s+)\S/ indent = $1 body = body.map {|b| b.gsub(/^#{indent}/, "")} end body.join("\n") end # Dispatches a message to an agent, accepts a block that will get run if there are # any replies to process from the agent def dispatch(request, connection) Log.debug("Dispatching a message to agent #{request.agent}") Thread.new do begin agent = PluginManager["#{request.agent}_agent"] Timeout::timeout(agent.timeout) do replies = agent.handlemsg(request.payload, connection) # Agents can decide if they wish to reply or not, # returning nil will mean nothing goes back to the # requestor unless replies == nil yield(replies) end end rescue Timeout::Error => e Log.warn("Timeout while handling message for #{request.agent}") rescue Exception => e Log.error("Execution of #{request.agent} failed: #{e}") Log.error(e.backtrace.join("\n\t\t")) end end end # Get a list of agents that we have def self.agentlist @@agents.keys end end end mcollective-2.0.0/lib/mcollective/logger/0000755000175000017500000000000011747546503017374 5ustar jonasjonasmcollective-2.0.0/lib/mcollective/logger/base.rb0000644000175000017500000000422011747546503020631 0ustar jonasjonasmodule MCollective module Logger # A base class for logging providers. # # Logging providers should provide the following: # # * start - all you need to do to setup your logging # * set_logging_level - set your logging to :info, :warn, etc # * valid_levels - a hash of maps from :info to your internal level name # * log - what needs to be done to log a specific message class Base attr_reader :active_level def initialize @known_levels = [:debug, :info, :warn, :error, :fatal] # Sanity check the class that impliments the logging @known_levels.each do |lvl| raise "Logger class did not specify a map for #{lvl}" unless valid_levels.include?(lvl) end end # Figures out the next level and sets it def cycle_level lvl = get_next_level set_level(lvl) log(lvl, "", "Logging level is now #{lvl.to_s.upcase}") end # Sets a new level and record it in @active_level def set_level(level) set_logging_level(level) @active_level = level.to_sym end private def map_level(level) raise "Logger class do not know how to handle #{level} messages" unless valid_levels.include?(level.to_sym) valid_levels[level.to_sym] end # Gets the next level in the list, cycles down to the firt once it reaches the end def get_next_level # if all else fails, always go to debug mode nextlvl = :debug if @known_levels.index(@active_level) == (@known_levels.size - 1) nextlvl = @known_levels.first else idx = @known_levels.index(@active_level) + 1 nextlvl = @known_levels[idx] end nextlvl end # Abstract methods to ensure the logging implimentations supply what they should def valid_levels raise "The logging class did not supply a valid_levels method" end def log(level, from, msg) raise "The logging class did not supply a log method" end def start raise "The logging class did not supply a start method" end end end end mcollective-2.0.0/lib/mcollective/logger/file_logger.rb0000644000175000017500000000242711747546503022204 0ustar jonasjonasrequire 'logger' module MCollective module Logger # Impliments a file based logger using the standard ruby logger class # # To configure you should set: # # - config.logfile # - config.keeplogs defaults to 2097152 # - config.max_log_size defaults to 5 class File_logger e @logger.level = ::Logger::DEBUG log(:error, "", "Could not set logging to #{level} using debug instead: #{e.class} #{e}") end def valid_levels {:info => ::Logger::INFO, :warn => ::Logger::WARN, :debug => ::Logger::DEBUG, :fatal => ::Logger::FATAL, :error => ::Logger::ERROR} end def log(level, from, msg) @logger.add(map_level(level)) { "#{from} #{msg}" } rescue # if this fails we probably cant show the user output at all, # STDERR it as last resort STDERR.puts("#{level}: #{msg}") end end end end mcollective-2.0.0/lib/mcollective/logger/console_logger.rb0000644000175000017500000000320511747546503022722 0ustar jonasjonasmodule MCollective module Logger # Impliments a syslog based logger using the standard ruby syslog class class Console_logger :info, :warn => :warning, :debug => :debug, :fatal => :crit, :error => :err} end def log(level, from, msg) if @known_levels.index(level) >= @known_levels.index(@active_level) time = Time.new.strftime("%Y/%m/%d %H:%M:%S") lvltxt = colorize(level, level) STDERR.puts("#{lvltxt} #{time}: #{from} #{msg}") end rescue # if this fails we probably cant show the user output at all, # STDERR it as last resort STDERR.puts("#{level}: #{msg}") end # Set some colors for various logging levels, will honor the # color configuration option and return nothing if its configured # not to def color(level) colorize = Config.instance.color colors = {:error => "", :fatal => "", :warn => "", :info => "", :reset => ""} if colorize return colors[level] || "" else return "" end end # Helper to return a string in specific color def colorize(level, msg) "#{self.color(level)}#{msg}#{self.color(:reset)}" end end end end mcollective-2.0.0/lib/mcollective/logger/syslog_logger.rb0000644000175000017500000000246511747546503022607 0ustar jonasjonasmodule MCollective module Logger # Implements a syslog based logger using the standard ruby syslog class class Syslog_logger e STDERR.puts "Invalid syslog facility #{facility} supplied, reverting to USER" Syslog::LOG_USER end end def set_logging_level(level) # noop end def valid_levels {:info => :info, :warn => :warning, :debug => :debug, :fatal => :crit, :error => :err} end def log(level, from, msg) if @known_levels.index(level) >= @known_levels.index(@active_level) Syslog.send(map_level(level), "#{from} #{msg}") end rescue # if this fails we probably cant show the user output at all, # STDERR it as last resort STDERR.puts("#{level}: #{msg}") end end end end mcollective-2.0.0/lib/mcollective/matcher.rb0000644000175000017500000000116211747546503020065 0ustar jonasjonasmodule MCollective # A parser and scanner that creates a stack machine for a simple # fact and class matching language used on the CLI to facilitate # a rich discovery language # # Language EBNF # # compound = ["("] expression [")"] {["("] expression [")"]} # expression = [!|not]statement ["and"|"or"] [!|not] statement # char = A-Z | a-z | < | > | => | =< | _ | - |* | / { A-Z | a-z | < | > | => | =< | _ | - | * | / | } # int = 0|1|2|3|4|5|6|7|8|9{|0|1|2|3|4|5|6|7|8|9|0} module Matcher autoload :Parser, "mcollective/matcher/parser" autoload :Scanner, "mcollective/matcher/scanner" end end mcollective-2.0.0/lib/mcollective/windows_daemon.rb0000644000175000017500000000115311747546503021457 0ustar jonasjonasrequire 'win32/daemon' module MCollective class WindowsDaemon < Win32::Daemon def self.daemonize_runner(pid=nil) raise "Writing pid files are not supported on the Windows Platform" if pid raise "The Windows Daemonizer should only be used on the Windows Platform" unless Util.windows? WindowsDaemon.mainloop end def service_main Log.debug("Starting Windows Service Daemon") runner = Runner.new(nil) runner.run end def service_stop Log.info("Windows service stopping") PluginManager["connector_plugin"].disconnect exit! 0 end end end mcollective-2.0.0/lib/mcollective/message.rb0000644000175000017500000001726611747546503020102 0ustar jonasjonasmodule MCollective # container for a message, its headers, agent, collective and other meta data class Message attr_reader :message, :request, :validated, :msgtime, :payload, :type, :expected_msgid, :reply_to attr_accessor :headers, :agent, :collective, :filter attr_accessor :requestid, :discovered_hosts, :options, :ttl VALIDTYPES = [:message, :request, :direct_request, :reply] # payload - the message body without headers etc, just the text # message - the original message received from the middleware # options[:base64] - if the body base64 encoded? # options[:agent] - the agent the message is for/from # options[:collective] - the collective its for/from # options[:headers] - the message headers # options[:type] - an indicator about the type of message, :message, :request, :direct_request or :reply # options[:request] - if this is a reply this should old the message we are replying to # options[:filter] - for requests, the filter to encode into the message # options[:options] - the normal client options hash # options[:ttl] - the maximum amount of seconds this message can be valid for # options[:expected_msgid] - in the case of replies this is the msgid it is expecting in the replies def initialize(payload, message, options = {}) options = {:base64 => false, :agent => nil, :headers => {}, :type => :message, :request => nil, :filter => Util.empty_filter, :options => {}, :ttl => 60, :expected_msgid => nil, :collective => nil}.merge(options) @payload = payload @message = message @requestid = nil @discovered_hosts = nil @reply_to = nil @type = options[:type] @headers = options[:headers] @base64 = options[:base64] @filter = options[:filter] @expected_msgid = options[:expected_msgid] @options = options[:options] @ttl = @options[:ttl] || Config.instance.ttl @msgtime = 0 @validated = false if options[:request] @request = options[:request] @agent = request.agent @collective = request.collective @type = :reply else @agent = options[:agent] @collective = options[:collective] end base64_decode! end # Sets the message type to one of the known types. In the case of :direct_request # the list of hosts to communicate with should have been set with #discovered_hosts # else an exception will be raised. This is for extra security, we never accidentally # want to send a direct request without a list of hosts or something weird like that # as it might result in a filterless broadcast being sent. # # Additionally you simply cannot set :direct_request if direct_addressing was not enabled # this is to force a workflow that doesnt not yield in a mistake when someone might assume # direct_addressing is enabled when its not. def type=(type) if type == :direct_request raise "Direct requests is not enabled using the direct_addressing config option" unless Config.instance.direct_addressing unless @discovered_hosts && !@discovered_hosts.empty? raise "Can only set type to :direct_request if discovered_hosts have been set" end end raise "Unknown message type #{type}" unless VALIDTYPES.include?(type) @type = type end # Sets a custom reply-to target for requests. The connector plugin should inspect this # when constructing requests and set this header ensuring replies will go to the custom target # otherwise the connector should just do what it usually does def reply_to=(target) raise "Custom reply targets can only be set on requests" unless [:request, :direct_request].include?(@type) @reply_to = target end # in the case of reply messages we are expecting replies to a previously # created message. This stores a hint to that previously sent message id # and can be used by other classes like the security plugins as a means # of optimizing their behavior like by ignoring messages not directed # at us. def expected_msgid=(msgid) raise "Can only store the expected msgid for reply messages" unless @type == :reply @expected_msgid = msgid end def base64_decode! return unless @base64 @payload = SSL.base64_decode(@payload) @base64 = false end def base64_encode! return if @base64 @payload = SSL.base64_encode(@payload) @base64 = true end def base64? @base64 end def encode! case type when :reply raise "Cannot encode a reply message if no request has been associated with it" unless request raise 'callerid in original request is not valid, surpressing reply to potentially forged request' unless PluginManager["security_plugin"].valid_callerid?(request.payload[:callerid]) @requestid = request.payload[:requestid] @payload = PluginManager["security_plugin"].encodereply(agent, payload, requestid, request.payload[:callerid]) when :request, :direct_request @requestid = create_reqid @payload = PluginManager["security_plugin"].encoderequest(Config.instance.identity, payload, requestid, filter, agent, collective, ttl) else raise "Cannot encode #{type} messages" end end def decode! raise "Cannot decode message type #{type}" unless [:request, :reply].include?(type) @payload = PluginManager["security_plugin"].decodemsg(self) if type == :request raise 'callerid in request is not valid, surpressing reply to potentially forged request' unless PluginManager["security_plugin"].valid_callerid?(payload[:callerid]) end [:collective, :agent, :filter, :requestid, :ttl, :msgtime].each do |prop| instance_variable_set("@#{prop}", payload[prop]) if payload.include?(prop) end end # Perform validation against the message by checking filters and ttl def validate raise "Can only validate request messages" unless type == :request msg_age = Time.now.utc.to_i - msgtime if msg_age > ttl cid = "" cid += payload[:callerid] + "@" if payload.include?(:callerid) cid += payload[:senderid] if msg_age > ttl PluginManager["global_stats"].ttlexpired raise(MsgTTLExpired, "Message #{requestid} from #{cid} created at #{msgtime} is #{msg_age} seconds old, TTL is #{ttl}") end end raise(NotTargettedAtUs, "Received message is not targetted to us") unless PluginManager["security_plugin"].validate_filter?(payload[:filter]) @validated = true end # publish a reply message by creating a target name and sending it def publish Timeout.timeout(2) do # If we've been specificaly told about hosts that were discovered # use that information to do P2P calls if appropriate else just # send it as is. if @discovered_hosts && Config.instance.direct_addressing if @discovered_hosts.size <= Config.instance.direct_addressing_threshold @type = :direct_request Log.debug("Handling #{requestid} as a direct request") end PluginManager["connector_plugin"].publish(self) else PluginManager["connector_plugin"].publish(self) end end end def create_reqid Digest::MD5.hexdigest("#{Config.instance.identity}-#{Time.now.to_f}-#{agent}-#{collective}") end end end mcollective-2.0.0/lib/mcollective/application.rb0000644000175000017500000002503511747546503020752 0ustar jonasjonasmodule MCollective class Application include RPC class << self # Intialize a blank set of options if its the first time used # else returns active options def application_options intialize_application_options unless @application_options @application_options end # set an option in the options hash def []=(option, value) intialize_application_options unless @application_options @application_options[option] = value end # retrieves a specific option def [](option) intialize_application_options unless @application_options @application_options[option] end # Sets the application description, there can be only one # description per application so multiple calls will just # change the description def description(descr) self[:description] = descr end # Supplies usage information, calling multiple times will # create multiple usage lines in --help output def usage(usage) self[:usage] << usage end def exclude_argument_sections(*sections) sections = [sections].flatten sections.each do |s| raise "Unknown CLI argument section #{s}" unless ["rpc", "common", "filter"].include?(s) end intialize_application_options unless @application_options self[:exclude_arg_sections] = sections end # Wrapper to create command line options # # - name: varaible name that will be used to access the option value # - description: textual info shown in --help # - arguments: a list of possible arguments that can be used # to activate this option # - type: a data type that ObjectParser understand of :bool or :array # - required: true or false if this option has to be supplied # - validate: a proc that will be called with the value used to validate # the supplied value # # option :foo, # :description => "The foo option" # :arguments => ["--foo ARG"] # # after this the value supplied will be in configuration[:foo] def option(name, arguments) opt = {:name => name, :description => nil, :arguments => [], :type => String, :required => false, :validate => Proc.new { true }} arguments.each_pair{|k,v| opt[k] = v} self[:cli_arguments] << opt end # Creates an empty set of options def intialize_application_options @application_options = {:description => nil, :usage => [], :cli_arguments => [], :exclude_arg_sections => []} end end # The application configuration built from CLI arguments def configuration @application_configuration ||= {} @application_configuration end # The active options hash used for MC::Client and other configuration def options @options end # Calls the supplied block in an option for validation, an error raised # will log to STDERR and exit the application def validate_option(blk, name, value) validation_result = blk.call(value) unless validation_result == true STDERR.puts "Validation of #{name} failed: #{validation_result}" exit 1 end end # Creates a standard options hash, pass in a block to add extra headings etc # see Optionparser def clioptions(help) oparser = Optionparser.new({:verbose => false, :progress_bar => true}, "filter", application_options[:exclude_arg_sections]) options = oparser.parse do |parser, options| if block_given? yield(parser, options) end RPC::Helpers.add_simplerpc_options(parser, options) unless application_options[:exclude_arg_sections].include?("rpc") end return oparser.parser.help if help validate_cli_options post_option_parser(configuration) if respond_to?(:post_option_parser) return options rescue Exception => e application_failure(e) end # Builds an ObjectParser config, parse the CLI options and validates based # on the option config def application_parse_options(help=false) @options ||= {:verbose => false} @options = clioptions(help) do |parser, options| parser.define_head application_description if application_description parser.banner = "" if application_usage parser.separator "" application_usage.each do |u| parser.separator "Usage: #{u}" end parser.separator "" end parser.define_tail "" parser.define_tail "The Marionette Collective #{MCollective.version}" application_cli_arguments.each do |carg| opts_array = [] opts_array << :on # if a default is set from the application set it up front if carg.include?(:default) configuration[carg[:name]] = carg[:default] end # :arguments are multiple possible ones if carg[:arguments].is_a?(Array) carg[:arguments].each {|a| opts_array << a} else opts_array << carg[:arguments] end # type was given and its not one of our special types, just pass it onto optparse opts_array << carg[:type] if carg[:type] and ! [:bool, :array].include?(carg[:type]) opts_array << carg[:description] # Handle our special types else just rely on the optparser to handle the types if carg[:type] == :bool parser.send(*opts_array) do |v| validate_option(carg[:validate], carg[:name], v) configuration[carg[:name]] = true end elsif carg[:type] == :array parser.send(*opts_array) do |v| validate_option(carg[:validate], carg[:name], v) configuration[carg[:name]] = [] unless configuration.include?(carg[:name]) configuration[carg[:name]] << v end else parser.send(*opts_array) do |v| validate_option(carg[:validate], carg[:name], v) configuration[carg[:name]] = v end end end end end def validate_cli_options # Check all required parameters were set validation_passed = true application_cli_arguments.each do |carg| # Check for required arguments if carg[:required] unless configuration[ carg[:name] ] validation_passed = false STDERR.puts "The #{carg[:name]} option is mandatory" end end end unless validation_passed STDERR.puts "\nPlease run with --help for detailed help" exit 1 end end # Retrieves the full hash of application options def application_options self.class.application_options end # Retrieve the current application description def application_description application_options[:description] end # Return the current usage text false if nothing is set def application_usage usage = application_options[:usage] usage.empty? ? false : usage end # Returns an array of all the arguments built using # calls to optin def application_cli_arguments application_options[:cli_arguments] end # Handles failure, if we're far enough in the initialization # phase it will log backtraces if its in verbose mode only def application_failure(e, err_dest=STDERR) # peole can use exit() anywhere and not get nasty backtraces as a result if e.is_a?(SystemExit) disconnect raise(e) end err_dest.puts "#{$0} failed to run: #{e} (#{e.class})" if options.nil? || options[:verbose] e.backtrace.each{|l| err_dest.puts "\tfrom #{l}"} end disconnect exit 1 end def help application_parse_options(true) end # The main logic loop, builds up the options, validate configuration and calls # the main as supplied by the user. Disconnects when done and pass any exception # onto the application_failure helper def run application_parse_options validate_configuration(configuration) if respond_to?(:validate_configuration) Util.setup_windows_sleeper if Util.windows? main disconnect rescue Exception => e application_failure(e) end def disconnect MCollective::PluginManager["connector_plugin"].disconnect rescue end # Fake abstract class that logs if the user tries to use an application without # supplying a main override method. def main STDERR.puts "Applications need to supply a 'main' method" exit 1 end # A helper that creates a consistent exit code for applications by looking at an # instance of MCollective::RPC::Stats # # Exit with 0 if nodes were discovered and all passed # Exit with 0 if no discovery were done and > 0 responses were received # Exit with 1 if no nodes were discovered # Exit with 2 if nodes were discovered but some RPC requests failed # Exit with 3 if nodes were discovered, but not responses receivedif # Exit with 4 if no discovery were done and no responses were received def halt(stats) request_stats = {:discoverytime => 0, :discovered => 0, :failcount => 0}.merge(stats.to_hash) # was discovery done? if request_stats[:discoverytime] != 0 # was any nodes discovered if request_stats[:discovered] == 0 exit 1 # nodes were discovered, did we get responses elsif request_stats[:responses] == 0 exit 3 else # we got responses and discovery was done, no failures if request_stats[:failcount] == 0 exit 0 else exit 2 end end else # discovery wasnt done and we got no responses if request_stats[:responses] == 0 exit 4 else exit 0 end end end # Wrapper around MC::RPC#rpcclient that forcably supplies our options hash # if someone forgets to pass in options in an application the filters and other # cli options wouldnt take effect which could have a disasterous outcome def rpcclient(agent, flags = {}) flags[:options] = options unless flags.include?(:options) super end end end mcollective-2.0.0/lib/mcollective.rb0000644000175000017500000000446611747546503016454 0ustar jonasjonasrequire 'rubygems' require 'stomp' require 'timeout' require 'digest/md5' require 'optparse' require 'singleton' require 'socket' require 'erb' require 'shellwords' require 'rbconfig' require 'tempfile' require 'tmpdir' require 'mcollective/monkey_patches' # == The Marionette Collective # # Framework to build and run Systems Administration agents running on a # publish/subscribe middleware system. The system allows you to treat your # network as the only true source of the state of your platform via discovery agents # and allow you to run agents matching discovery criteria. # # For an overview of the idea behind this and what it enables please see: # http://www.devco.net/archives/2009/10/18/middleware_for_systems_administration.php module MCollective # Exceptions for the RPC system class RPCError true}, "filter") options = oparser.parse{|parser, options| parser.define_head "Call an agent parsing an argument to it" parser.banner = "Usage: mc-call-agent [options] --agent agent --argument arg" parser.on('-a', '--agent AGENT', 'Agent to call') do |v| options[:agent] = v end parser.on('--arg', '--argument ARGUMENT', 'Argument to pass to agent') do |v| options[:argument] = v end } if options[:agent] == nil || options[:argument] == nil puts("Please use either --agent or --argument") exit 1 end begin options[:filter]["agent"] << options[:agent] client = MCollective::Client.new(options[:config]) client.options = options c = 0 stats = client.discovered_req(options[:argument], options[:agent]) do |resp| next if resp == nil c += 1 if options[:verbose] puts("#{resp[:senderid]}>") pp resp[:body] else puts if c % 4 == 1 printf("%-30s", resp[:senderid]) end end client.disconnect rescue Exception => e STDERR.puts "Could not call remote agent: #{e}" exit 1 end client.display_stats(stats) mcollective-2.0.0/bin/mco0000755000175000017500000000156411747546503014323 0ustar jonasjonas#!/usr/bin/env ruby require 'mcollective' Version = MCollective.version known_applications = MCollective::Applications.list # links from mc-ping to mc will result in ping being run if $0 =~ /mc\-([a-zA-Z\-_\.]+)$/ app_name = $1 else app_name = ARGV.first ARGV.delete_at(0) end if known_applications.include?(app_name) # make sure the various options classes shows the right help etc $0 = app_name MCollective::Applications.run(app_name) else puts "The Marionette Collective version #{MCollective.version}" puts puts "usage: #{$0} command " puts puts "Known commands:" puts known_applications.sort.uniq.in_groups_of(3) do |apps| puts " %-20s %-20s %-20s" % [apps[0], apps[1], apps[2]] end puts puts "Type '#{$0} help' for a detailed list of commands and '#{$0} help command'" puts "to get detailed help for a command" puts end mcollective-2.0.0/COPYING0000644000175000017500000002612411747546503014101 0ustar jonasjonas Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2010, 2011 Puppet Labs Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. mcollective-2.0.0/plugins/0000755000175000017500000000000011747546503014522 5ustar jonasjonasmcollective-2.0.0/plugins/mcollective/0000755000175000017500000000000011747546503017030 5ustar jonasjonasmcollective-2.0.0/plugins/mcollective/application/0000755000175000017500000000000011747546503021333 5ustar jonasjonasmcollective-2.0.0/plugins/mcollective/application/find.rb0000644000175000017500000000056511747546503022606 0ustar jonasjonasclass MCollective::Application::Find [--argument ] The COMMAND can be one of the following: stats - retrieve statistics from the mcollectived reload_agent - reloads an agent, requires an agent name as argument reload_agents - reloads all agents END_OF_USAGE option :argument, :description => "Argument to pass to an agent", :arguments => [ '-a', '--arg', '--argument ARGUMENT' ], :type => String def print_statistics(sender, statistics) printf("%40s> total=%d, replies=%d, valid=%d, invalid=%d, " + "filtered=%d, passed=%d\n", sender, statistics[:total], statistics[:replies], statistics[:validated], statistics[:unvalidated], statistics[:filtered], statistics[:passed]) end def post_option_parser(configuration) configuration[:command] = ARGV.shift if ARGV.size > 0 end def validate_configuration(configuration) unless configuration.include?(:command) raise "Please specify a command and optional arguments" end # # When asked to restart an agent we need to make sure that # we have this agent name and set appropriate filters ... # if configuration[:command].match(/^reload_agent$/) unless configuration.include?(:argument) raise "Please specify an agent name to reload with --argument" end options[:filter]['agent'] << configuration[:argument] end end def main client = MCollective::Client.new(options[:config]) client.options = options counter = 0 command = configuration[:command] command += " #{configuration[:argument]}" if configuration[:argument] statistics = client.discovered_req(command, 'mcollective') do |response| next unless response counter += 1 sender = response[:senderid] body = response[:body] case command when /^stats$/ print_statistics(sender, body[:stats]) when /^reload_agent(?:.+)/ printf("%40s> %s\n", sender, body) else if options[:verbose] puts "#{sender}>" pp body else puts if counter % 4 == 1 print "#{sender} " end end end client.disconnect client.display_stats(statistics, false, "mcollectived controller summary") halt statistics end end end mcollective-2.0.0/plugins/mcollective/application/rpc.rb0000644000175000017500000001133111747546503022443 0ustar jonasjonasclass MCollective::Application::Rpc --action [--argument --argument ...]" usage "mco rpc [options] [filters] [ ...]" option :no_results, :description => "Do not process results, just send request", :arguments => ["--no-results", "--nr"], :default => false, :type => :bool option :agent, :description => "Agent to call", :arguments => ["-a", "--agent AGENT"] option :action, :description => "Action to call", :arguments => ["--action ACTION"] option :arguments, :description => "Arguments to pass to agent", :arguments => ["--arg", "--argument ARGUMENT"], :type => :array, :default => [], :validate => Proc.new {|val| val.match(/^(.+?)=(.+)$/) ? true : "Could not parse --arg #{val} should be of the form key=val" } def post_option_parser(configuration) # handle the alternative format that optparse cant parse unless (configuration.include?(:agent) && configuration.include?(:action)) if ARGV.length >= 2 configuration[:agent] = ARGV[0] ARGV.delete_at(0) configuration[:action] = ARGV[0] ARGV.delete_at(0) ARGV.each do |v| if v =~ /^(.+?)=(.+)$/ configuration[:arguments] = [] unless configuration.include?(:arguments) configuration[:arguments] << v else STDERR.puts("Could not parse --arg #{v}") end end else STDERR.puts("No agent, action and arguments specified") exit! end end # convert arguments to symbols for keys to comply with simplerpc conventions args = configuration[:arguments].clone configuration[:arguments] = {} args.each do |v| if v =~ /^(.+?)=(.+)$/ configuration[:arguments][$1.to_sym] = $2 end end end # As we're taking arguments on the command line we need a # way to input booleans, true on the cli is a string so this # method will take the ddl, find all arguments that are supposed # to be boolean and if they are the strings "true"/"yes" or "false"/"no" # turn them into the matching boolean def string_to_boolean(val) return true if ["true", "yes", "1"].include?(val) return false if ["false", "no", "0"].include?(val) raise "#{val} does not look like a boolean argument" end # a generic string to number function, if a number looks like a float # it turns it into a float else an int. This is naive but should be sufficient # for numbers typed on the cli in most cases def string_to_number(val) return val.to_f if val =~ /^\d+\.\d+$/ return val.to_i if val =~ /^\d+$/ raise "#{val} does not look like a number" end def string_to_ddl_type(arguments, ddl) return if ddl.empty? arguments.keys.each do |key| if ddl[:input].keys.include?(key) begin case ddl[:input][key][:type] when :boolean arguments[key] = booleanish_to_boolean(arguments[key]) when :number, :integer, :float arguments[key] = string_to_number(arguments[key]) end rescue # just go on to the next key, DDL validation will figure out # any inconsistancies caused by exceptions when the request is made end end end end def main mc = rpcclient(configuration[:agent]) mc.agent_filter(configuration[:agent]) string_to_ddl_type(configuration[:arguments], mc.ddl.action_interface(configuration[:action])) unless mc.ddl.nil? if mc.reply_to configuration[:arguments][:process_results] = true puts "Request sent with id: " + mc.send(configuration[:action], configuration[:arguments]) + " replies to #{mc.reply_to}" elsif configuration[:no_results] configuration[:arguments][:process_results] = false puts "Request sent with id: " + mc.send(configuration[:action], configuration[:arguments]) else # if there's stuff on STDIN assume its JSON that came from another # rpc or printrpc, we feed that in as discovery data discover_args = {:verbose => true} unless STDIN.tty? discovery_data = STDIN.read.chomp discover_args = {:json => discovery_data} unless discovery_data == "" end mc.discover discover_args printrpc mc.send(configuration[:action], configuration[:arguments]) printrpcstats :caption => "#{configuration[:agent]}##{configuration[:action]} call stats" if mc.discover.size > 0 halt mc.stats end end end mcollective-2.0.0/plugins/mcollective/application/help.rb0000644000175000017500000000121311747546503022605 0ustar jonasjonasmodule MCollective class Application::Help 0 end def main if configuration.include?(:application) puts Applications[configuration[:application]].help else puts "The Marionette Collective version #{MCollective.version}" puts Applications.list.sort.each do |app| puts " %-15s %s" % [app, Applications[app].application_description] end puts end end end end mcollective-2.0.0/plugins/mcollective/application/plugin.rb0000644000175000017500000001615411747546503023165 0ustar jonasjonasmodule MCollective class Application::Plugin mco plugin info mco plugin doc info : Display plugin information including package details. package : Create all available plugin packages. doc : Display documentation for a specific agent. END_OF_USAGE option :pluginname, :description => "Plugin name", :arguments => ["-n", "--name NAME"], :type => String option :postinstall, :description => "Post install script", :arguments => ["--postinstall POSTINSTALL"], :type => String option :preinstall, :description => "Pre install script", :arguments => ["--preinstall PREINSTALL"], :type => String option :iteration, :description => "Iteration number", :arguments => ["--iteration ITERATION"], :type => String option :vendor, :description => "Vendor name", :arguments => ["--vendor VENDOR"], :type => String option :pluginpath, :description => "MCollective plugin path", :arguments => ["--pluginpath PATH"], :type => String option :mccommon, :description => "Set the mcollective common package that the plugin depends on", :arguments => ["--mc-common-pkg PACKAGE"], :type => String option :mcserver, :description => "Set the mcollective server package that the plugin depends on", :arguments => ["--mc-server-pkg PACKAGE"], :type => String option :mcclient, :description => "Set the mcollective client package that the plugin depends on", :arguments => ["--mc-client-pkg PACKAGE"], :type =>String option :dependency, :description => "Adds a dependency to the plugin", :arguments => ["--dependency DEPENDENCIES"], :type => :array option :format, :description => "Package output format. Defaults to rpmpackage or debpackage", :arguments => ["--format OUTPUTFORMAT"], :type => String option :sign, :description => "Embed a signature in the package", :arguments => ["--sign"], :type => :boolean option :rpctemplate, :description => "RPC Template to use.", :arguments => ["--template RPCHELPTEMPLATE"], :type => String # Handle alternative format that optparser can't parse. def post_option_parser(configuration) if ARGV.length >= 1 configuration[:action] = ARGV.delete_at(0) configuration[:target] = ARGV.delete_at(0) || "." end end # Display info about plugin def info_command plugin = prepare_plugin packager = PluginPackager["#{configuration[:format].capitalize}Packager"] packager.new(plugin).package_information end # Package plugin def package_command if configuration[:sign] && Config.instance.pluginconf.include?("debian_packager.keyname") configuration[:sign] = Config.instance.pluginconf["debian_packager.keyname"] configuration[:sign] = "\"#{configuration[:sign]}\"" unless configuration[:sign].match(/\".*\"/) end plugin = prepare_plugin (configuration[:pluginpath] = configuration[:pluginpath] + "/") if (configuration[:pluginpath] && !configuration[:pluginpath].match(/^.*\/$/)) packager = PluginPackager["#{configuration[:format].capitalize}Packager"] packager.new(plugin, configuration[:pluginpath], configuration[:sign], configuration[:verbose]).create_packages end # Show application list and RPC agent help def doc_command if configuration.include?(:target) && configuration[:target] != "." ddl = MCollective::RPC::DDL.new(configuration[:target]) puts ddl.help(configuration[:rpctemplate] || Config.instance.rpchelptemplate) else puts "Please specify an agent. Available agents are:" puts PluginManager.find("agent", "ddl").each do |ddl| help = MCollective::RPC::DDL.new(ddl) puts " %-15s %s" % [ddl, help.meta[:description]] end puts end end # Creates the correct package plugin object. def prepare_plugin plugintype = set_plugin_type unless configuration[:plugintype] configuration[:format] = "ospackage" unless configuration[:format] PluginPackager.load_packagers plugin_class = PluginPackager[configuration[:plugintype]] configuration[:dependency] = configuration[:dependency][0].split(" ") if configuration[:dependency] && configuration[:dependency].size == 1 mcodependency = {:server => configuration[:mcserver], :client => configuration[:mcclient], :common => configuration[:mccommon]} plugin_class.new(configuration[:target], configuration[:pluginname], configuration[:vendor], configuration[:preinstall], configuration[:postinstall], configuration[:iteration], configuration[:dependency], mcodependency , plugintype) end def directory_for_type(type) File.directory?(File.join(configuration[:target], type)) end # Identify plugin type if not provided. def set_plugin_type if directory_for_type("agent") || directory_for_type("application") configuration[:plugintype] = "AgentDefinition" return "Agent" elsif directory_for_type(plugintype = identify_plugin) configuration[:plugintype] = "StandardDefinition" return plugintype else raise RuntimeError, "target directory is not a valid mcollective plugin" end end # If plugintype is StandardDefinition, identify which of the special # plugin types we are dealing with based on directory structure. # To keep it simple we limit it to one type per target directory. def identify_plugin plugintype = Dir.glob(File.join(configuration[:target], "*")).select do |file| File.directory?(file) && file.match(/(connector|facts|registration|security|audit|pluginpackager)/) end raise RuntimeError, "more than one plugin type detected in directory" if plugintype.size > 1 raise RuntimeError, "no plugins detected in directory" if plugintype.size < 1 stripdir = configuration[:target] == "." ? "" : configuration[:target] plugintype.first.gsub(/\.|\/|#{stripdir}/, "") end # Returns a list of available actions in a pretty format def list_actions methods.sort.grep(/_command/).map{|x| x.to_s.gsub("_command", "")}.join("|") end def main abort "No action specified" unless configuration.include?(:action) cmd = "#{configuration[:action]}_command" if respond_to? cmd send cmd else abort "Invalid action #{configuration[:action]}. Valid actions are [#{list_actions}]." end end end end mcollective-2.0.0/plugins/mcollective/application/facts.rb0000644000175000017500000000243111747546503022760 0ustar jonasjonasclass MCollective::Application::Facts 0 end def validate_configuration(configuration) raise "Please specify a fact to report for" unless configuration.include?(:fact) end def show_single_fact_report(fact, facts, verbose=false) puts("Report for fact: #{fact}\n\n") facts.keys.sort.each do |k| printf(" %-40sfound %d times\n", k, facts[k].size) if verbose puts facts[k].sort.each do |f| puts(" #{f}") end puts end end end def main rpcutil = rpcclient("rpcutil") rpcutil.progress = false facts = {} rpcutil.get_fact(:fact => configuration[:fact]) do |resp| begin value = resp[:body][:data][:value] if value facts.include?(value) ? facts[value] << resp[:senderid] : facts[value] = [ resp[:senderid] ] end rescue Exception => e STDERR.puts "Could not parse facts for #{resp[:senderid]}: #{e.class}: #{e}" end end show_single_fact_report(configuration[:fact], facts, options[:verbose]) printrpcstats halt rpcutil.stats end end mcollective-2.0.0/plugins/mcollective/application/ping.rb0000644000175000017500000000412311747546503022615 0ustar jonasjonas# encoding: utf-8 module MCollective class Application::Ping "Shows a graph of ping distribution", :arguments => ["--graph", "-g"], :default => false, :type => :bool # Convert the times structure into a array representing # buckets of responses in 50 ms intervals. Return a small # sparkline graph using UTF8 characters def spark(resp_times) return "" unless configuration[:graph] || Config.instance.pluginconf["rpc.graph"] ticks=%w[▁ ▂ ▃ ▄ ▅ ▆ ▇] histo = {} # round each time to its nearest 50ms # and keep a count for each 50ms resp_times.each do |time| time = Integer(time + 50 - (time % 50)) histo[time] ||= 0 histo[time] += 1 end # set the 50ms intervals that saw no traffic to 0 ((histo.keys.max - histo.keys.min) / 50).times do |i| time = (i * 50) + histo.keys.min histo[time] = 0 unless histo[time] end # get a numerically sorted list of times histo = histo.keys.sort.map{|k| histo[k]} range = histo.max - histo.min scale = ticks.size - 1 distance = histo.max.to_f / scale histo.map do |val| tick = (val / distance).round tick = 0 if tick < 0 ticks[tick] end.join end def main client = MCollective::Client.new(options[:config]) client.options = options start = Time.now.to_f times = [] client.req("ping", "discovery") do |resp| times << (Time.now.to_f - start) * 1000 puts "%-40s time=%.2f ms" % [resp[:senderid], times.last] end puts("\n\n---- ping statistics ----") if times.size > 0 sum = times.inject(0){|acc,i|acc +i} avg = sum / times.length.to_f puts "%d replies max: %.2f min: %.2f avg: %.2f %s" % [times.size, times.max, times.min, avg, spark(times)] else puts("No responses received") end halt client.stats end end end mcollective-2.0.0/plugins/mcollective/application/inventory.rb0000644000175000017500000002170311747546503023720 0ustar jonasjonasclass MCollective::Application::Inventory "Script to run", :arguments => ["--script SCRIPT"] option :collectives, :description => "List all known collectives", :arguments => ["--list-collectives", "--lc"], :default => false, :type => :bool option :collectivemap, :description => "Create a DOT graph of all collectives", :arguments => ["--collective-graph MAP", "--cg MAP", "--map MAP"] def post_option_parser(configuration) configuration[:node] = ARGV.shift if ARGV.size > 0 end def validate_configuration(configuration) unless configuration[:node] || configuration[:script] || configuration[:collectives] || configuration[:collectivemap] raise "Need to specify either a node name, script to run or other options" end end # Get all the known collectives and nodes that belong to them def get_collectives util = rpcclient("rpcutil") util.progress = false collectives = {} nodes = 0 total = 0 util.collective_info do |r, cinfo| begin if cinfo[:data] && cinfo[:data][:collectives] cinfo[:data][:collectives].each do |collective| collectives[collective] ||= [] collectives[collective] << cinfo[:sender] end nodes += 1 total += 1 end end end {:collectives => collectives, :nodes => nodes, :total_nodes => total} end # Writes a crude DOT graph to a file def collectives_map(file) File.open(file, "w") do |graph| puts "Retrieving collective info...." collectives = get_collectives graph.puts 'graph {' collectives[:collectives].keys.sort.each do |collective| graph.puts ' subgraph "%s" {' % [ collective ] collectives[:collectives][collective].each do |member| graph.puts ' "%s" -- "%s"' % [ member, collective ] end graph.puts ' }' end graph.puts '}' puts "Graph of #{collectives[:total_nodes]} nodes has been written to #{file}" end end # Prints a report of all known sub collectives def collectives_report collectives = get_collectives puts " %-30s %s" % [ "Collective", "Nodes" ] puts " %-30s %s" % [ "==========", "=====" ] collectives[:collectives].sort_by {|key,count| count.size}.each do |collective| puts " %-30s %d" % [ collective[0], collective[1].size ] end puts puts " %30s %d" % [ "Total nodes:", collectives[:nodes] ] puts end def node_inventory node = configuration[:node] util = rpcclient("rpcutil") util.identity_filter node util.progress = false nodestats = util.custom_request("daemon_stats", {}, node, {"identity" => node}).first unless nodestats STDERR.puts "Did not receive any results from node #{node}" exit 1 end unless nodestats[:statuscode] == 0 STDERR.puts "Failed to retrieve daemon_stats from #{node}: #{nodestats[:statusmsg]}" else util.custom_request("inventory", {}, node, {"identity" => node}).each do |resp| unless resp[:statuscode] == 0 STDERR.puts "Failed to retrieve inventory for #{node}: #{resp[:statusmsg]}" next end data = resp[:data] begin puts "Inventory for #{resp[:sender]}:" puts nodestats = nodestats[:data] puts " Server Statistics:" puts " Version: #{nodestats[:version]}" puts " Start Time: #{Time.at(nodestats[:starttime])}" puts " Config File: #{nodestats[:configfile]}" puts " Collectives: #{data[:collectives].join(', ')}" if data.include?(:collectives) puts " Main Collective: #{data[:main_collective]}" if data.include?(:main_collective) puts " Process ID: #{nodestats[:pid]}" puts " Total Messages: #{nodestats[:total]}" puts " Messages Passed Filters: #{nodestats[:passed]}" puts " Messages Filtered: #{nodestats[:filtered]}" puts " Expired Messages: #{nodestats[:ttlexpired]}" puts " Replies Sent: #{nodestats[:replies]}" puts " Total Processor Time: #{nodestats[:times][:utime]} seconds" puts " System Time: #{nodestats[:times][:stime]} seconds" puts puts " Agents:" if data[:agents].size > 0 data[:agents].sort.in_groups_of(3, "") do |agents| puts " %-15s %-15s %-15s" % agents end else puts " No agents installed" end puts puts " Configuration Management Classes:" if data[:classes].size > 0 data[:classes].sort.in_groups_of(2, "") do |klasses| puts " %-30s %-30s" % klasses end else puts " No classes applied" end puts puts " Facts:" if data[:facts].size > 0 data[:facts].sort_by{|f| f[0]}.each do |f| puts " #{f[0]} => #{f[1]}" end else puts " No facts known" end break rescue Exception => e STDERR.puts "Failed to display node inventory: #{e.class}: #{e}" end end end halt util.stats end # Helpers to create a simple DSL for scriptlets def format(fmt) @fmt = fmt end def fields(&blk) @flds = blk end def identity @node[:identity] end def facts @node[:facts] end def classes @node[:classes] end def agents @node[:agents] end def page_length(len) @page_length = len end def page_heading(fmt) @page_heading = fmt end def page_body(fmt) @page_body = fmt end # Expects a simple printf style format and apply it to # each node: # # inventory do # format "%s:\t\t%s\t\t%s" # # fields { [ identity, facts["serialnumber"], facts["productname"] ] } # end def inventory(&blk) raise "Need to give a block to inventory" unless block_given? blk.call if block_given? raise "Need to define a format" if @fmt.nil? raise "Need to define inventory fields" if @flds.nil? util = rpcclient("rpcutil") util.progress = false util.inventory do |t, resp| @node = {:identity => resp[:sender], :facts => resp[:data][:facts], :classes => resp[:data][:classes], :agents => resp[:data][:agents]} puts @fmt % @flds.call end end # Use the ruby formatr gem to build reports using Perls formats # # It is kind of ugly but brings a lot of flexibility in report # writing without building an entire reporting language. # # You need to have formatr installed to enable reports like: # # formatted_inventory do # page_length 20 # # page_heading < resp[:sender], :facts => resp[:data][:facts], :classes => resp[:data][:classes], :agents => resp[:data][:agents]} body_fmt.printFormat(binding) end rescue Exception => e STDERR.puts "Could not create report: #{e.class}: #{e}" exit 1 end @fmt = nil @flds = nil @page_heading = nil @page_body = nil @page_length = 40 def main if configuration[:script] if File.exist?(configuration[:script]) eval(File.read(configuration[:script])) else raise "Could not find script to run: #{configuration[:script]}" end elsif configuration[:collectivemap] collectives_map(configuration[:collectivemap]) elsif configuration[:collectives] collectives_report else node_inventory end end end mcollective-2.0.0/plugins/mcollective/agent/0000755000175000017500000000000011747546503020126 5ustar jonasjonasmcollective-2.0.0/plugins/mcollective/agent/discovery.rb0000644000175000017500000000426511747546503022471 0ustar jonasjonasmodule MCollective module Agent # Discovery agent for The Marionette Collective # # Released under the Apache License, Version 2 class Discovery attr_reader :timeout, :meta def initialize config = Config.instance.pluginconf @timeout = 5 @timeout = config["discovery.timeout"].to_i if config.include?("discovery.timeout") @meta = {:license => "Apache License, Version 2", :author => "R.I.Pienaar ", :timeout => @timeout, :name => "Discovery Agent", :version => MCollective.version, :url => "http://www.marionette-collective.org", :description => "MCollective Discovery Agent"} end def handlemsg(msg, stomp) reply = "unknown request" case msg[:body] when "inventory" reply = inventory when /echo (.+)/ reply = $1 when "ping" reply = "pong" when /^get_fact (.+)/ reply = Facts[$1] else reply = "Unknown Request: #{msg[:body]}" end reply end def help <<-EOH Discovery Agent =============== Agent to facilitate discovery of machines and data about machines. Accepted Messages ----------------- inventory - returns a hash with various bits of information like list of agents, threads, etc ping - simply responds with 'pong' get_fact fact - replies with the value of a facter fact EOH end private def inventory reply = {:agents => Agents.agentlist, :threads => [], :facts => {}, :classes => [], :times => ::Process.times} reply[:facts] = PluginManager["facts_plugin"].get_facts cfile = Config.instance.classesfile if File.exist?(cfile) reply[:classes] = File.readlines(cfile).map {|i| i.chomp} end Thread.list.each do |t| reply[:threads] << "#{t.inspect}" end reply end end end end mcollective-2.0.0/plugins/mcollective/agent/rpcutil.ddl0000644000175000017500000001156611747546503022306 0ustar jonasjonasmetadata :name => "Utilities and Helpers for SimpleRPC Agents", :description => "General helpful actions that expose stats and internals to SimpleRPC clients", :author => "R.I.Pienaar ", :license => "Apache License, Version 2.0", :version => "1.0", :url => "http://marionette-collective.org/", :timeout => 10 action "collective_info", :description => "Info about the main and sub collectives" do display :always output :main_collective, :description => "The main Collective", :display_as => "Main Collective" output :collectives, :description => "All Collectives", :display_as => "All Collectives" end action "inventory", :description => "System Inventory" do display :always output :agents, :description => "List of agent names", :display_as => "Agents" output :facts, :description => "List of facts and values", :display_as => "Facts" output :classes, :description => "List of classes on the system", :display_as => "Classes" output :version, :description => "MCollective Version", :display_as => "Version" output :main_collective, :description => "The main Collective", :display_as => "Main Collective" output :collectives, :description => "All Collectives", :display_as => "All Collectives" end action "get_fact", :description => "Retrieve a single fact from the fact store" do display :always input :fact, :prompt => "The name of the fact", :description => "The fact to retrieve", :type => :string, :validation => '^[\w\-\.]+$', :optional => false, :maxlength => 40 output :fact, :description => "The name of the fact being returned", :display_as => "Fact" output :value, :description => "The value of the fact", :display_as => "Value" end action "daemon_stats", :description => "Get statistics from the running daemon" do display :always output :threads, :description => "List of threads active in the daemon", :display_as => "Threads" output :agents, :description => "List of agents loaded", :display_as => "Agents" output :pid, :description => "Process ID of the daemon", :display_as => "PID" output :times, :description => "Processor time consumed by the daemon", :display_as => "Times" output :validated, :description => "Messages that passed security validation", :display_as => "Security Validated" output :unvalidated, :description => "Messages that failed security validation", :display_as => "Failed Security" output :passed, :description => "Passed filter checks", :display_as => "Passed Filter" output :filtered, :description => "Didn't pass filter checks", :display_as => "Failed Filter" output :starttime, :description => "Time the server started", :display_as => "Start Time" output :total, :description => "Total messages received", :display_as => "Total Messages" output :replies, :description => "Replies sent back to clients", :display_as => "Replies" output :configfile, :description => "Config file used to start the daemon", :display_as => "Config File" output :version, :description => "MCollective Version", :display_as => "Version" output :ttlexpired, :description => "Messages that did pass TTL checks", :display_as => "TTL Expired" end action "agent_inventory", :description => "Inventory of all agents on the server" do display :always output :agents, :description => "List of agents on the server", :display_as => "Agents" end action "get_config_item", :description => "Get the active value of a specific config property" do display :always input :item, :prompt => "Configuration Item", :description => "The item to retrieve from the server", :type => :string, :validation => '^.+$', :optional => false, :maxlength => 50 output :item, :description => "The config property being retrieved", :display_as => "Property" output :value, :description => "The value that is in use", :display_as => "Value" end action "ping", :description => "Responds to requests for PING with PONG" do display :always output :pong, :description => "The local timestamp", :display_as => "Timestamp" end mcollective-2.0.0/plugins/mcollective/agent/rpcutil.rb0000644000175000017500000000604011747546503022135 0ustar jonasjonasmodule MCollective module Agent class Rpcutil "Utilities and Helpers for SimpleRPC Agents", :description => "General helpful actions that expose stats and internals to SimpleRPC clients", :author => "R.I.Pienaar ", :license => "Apache License, Version 2.0", :version => "1.0", :url => "http://marionette-collective.org/", :timeout => 10 # Basic system inventory, same as the basic discovery agent action "inventory" do reply[:agents] = Agents.agentlist reply[:facts] = PluginManager["facts_plugin"].get_facts reply[:version] = MCollective.version reply[:classes] = [] reply[:main_collective] = config.main_collective reply[:collectives] = config.collectives cfile = Config.instance.classesfile if File.exist?(cfile) reply[:classes] = File.readlines(cfile).map {|i| i.chomp} end end # Retrieve a single fact from the node action "get_fact" do validate :fact, String reply[:fact] = request[:fact] reply[:value] = Facts[request[:fact]] end # Get the global stats for this mcollectied action "daemon_stats" do stats = PluginManager["global_stats"].to_hash reply[:threads] = stats[:threads] reply[:agents] = stats[:agents] reply[:pid] = stats[:pid] reply[:times] = stats[:times] reply[:configfile] = Config.instance.configfile reply[:version] = MCollective.version reply.data.merge!(stats[:stats]) end # Builds an inventory of all agents on teh machine # including license, version and timeout information action "agent_inventory" do reply[:agents] = [] Agents.agentlist.sort.each do |target_agent| agent = PluginManager["#{target_agent}_agent"] actions = agent.methods.grep(/_agent/) agent_data = {:agent => target_agent, :license => "unknown", :timeout => agent.timeout, :description => "unknown", :name => target_agent, :url => "unknown", :version => "unknown", :author => "unknown"} agent_data.merge!(agent.meta) reply[:agents] << agent_data end end # Retrieves a single config property that is in effect action "get_config_item" do validate :item, String reply.fail! "Unknown config property #{request[:item]}" unless config.respond_to?(request[:item]) reply[:item] = request[:item] reply[:value] = config.send(request[:item]) end # Responds to PING requests with the local timestamp action "ping" do reply[:pong] = Time.now.to_i end # Returns all configured collectives action "collective_info" do config = Config.instance reply[:main_collective] = config.main_collective reply[:collectives] = config.collectives end end end end mcollective-2.0.0/plugins/mcollective/security/0000755000175000017500000000000011747546503020677 5ustar jonasjonasmcollective-2.0.0/plugins/mcollective/security/ssl.rb0000644000175000017500000003033011747546503022024 0ustar jonasjonasrequire 'base64' require 'openssl' module MCollective module Security # Impliments a public/private key based message validation system using SSL # public and private keys. # # The design goal of the plugin is two fold: # # - give different security credentials to clients and servers to avoid # a compromised server from sending new client requests. # - create a token that uniquely identify the client - based on the filename # of the public key # # To setup you need to create a SSL key pair that is shared by all nodes. # # openssl genrsa -out mcserver-private.pem 1024 # openssl rsa -in mcserver-private.pem -out mcserver-public.pem -outform PEM -pubout # # Distribute the private and public file to /etc/mcollective/ssl on all the nodes. # Distribute the public file to /etc/mcollective/ssl everywhere the client code runs. # # Now you should create a key pair for every one of your clients, here we create one # for user john - you could also if you are less concerned with client id create one # pair and share it with all clients: # # openssl genrsa -out john-private.pem 1024 # openssl rsa -in john-private.pem -out john-public.pem -outform PEM -pubout # # Each user has a unique userid, this is based on the name of the public key. # In this example case the userid would be 'john-public'. # # Store these somewhere like: # # /home/john/.mc/john-private.pem # /home/john/.mc/john-public.pem # # Every users public key needs to be distributed to all the nodes, save the john one # in a file called: # # /etc/mcollective/ssl/clients/john-public.pem # # If you wish to use registration or auditing that sends connections over MC to a # central host you will need also put the server-public.pem in the clients directory. # # You should be aware if you do add the node public key to the clients dir you will in # effect be weakening your overall security. You should consider doing this only if # you also set up an Authorization method that limits the requests the nodes can make. # # client.cfg: # # securityprovider = ssl # plugin.ssl_server_public = /etc/mcollective/ssl/server-public.pem # plugin.ssl_client_private = /home/john/.mc/john-private.pem # plugin.ssl_client_public = /home/john/.mc/john-public.pem # # If you have many clients per machine and dont want to configure the main config file # with the public/private keys you can set the following environment variables: # # export MCOLLECTIVE_SSL_PRIVATE=/home/john/.mc/john-private.pem # export MCOLLECTIVE_SSL_PUBLIC=/home/john/.mc/john-public.pem # # server.cfg: # # securityprovider = ssl # plugin.ssl_server_private = /etc/mcollective/ssl/server-private.pem # plugin.ssl_server_public = /etc/mcollective/ssl/server-public.pem # plugin.ssl_client_cert_dir = /etc/mcollective/etc/ssl/clients/ # # # Log but accept messages that may have been tampered with # plugin.ssl.enforce_ttl = 0 # # Serialization can be configured to use either Marshal or YAML, data types # in and out of mcollective will be preserved from client to server and reverse # # You can configure YAML serialization: # # plugins.ssl_serializer = yaml # # else the default is Marshal. Use YAML if you wish to write a client using # a language other than Ruby that doesn't support Marshal. # # Validation is as default and is provided by MCollective::Security::Base # # Initial code was contributed by Vladimir Vuksan and modified by R.I.Pienaar class Ssl < Base # Decodes a message by unserializing all the bits etc, it also validates # it as valid using the psk etc def decodemsg(msg) body = deserialize(msg.payload) should_process_msg?(msg, body[:requestid]) if validrequest?(body) body[:body] = deserialize(body[:body]) unless @initiated_by == :client if body[:body].is_a?(Hash) update_secure_property(body, :ssl_ttl, :ttl, "TTL") update_secure_property(body, :ssl_msgtime, :msgtime, "Message Time") body[:body] = body[:body][:ssl_msg] if body[:body].include?(:ssl_msg) else unless @config.pluginconf["ssl.enforce_ttl"] == nil raise "Message %s is in an unknown or older security protocol, ignoring" % [request_description(body)] end end end return body else nil end end # To avoid tampering we turn the origin body into a hash and copy some of the protocol keys # like :ttl and :msg_time into the hash before hashing it. # # This function compares and updates the unhashed ones based on the hashed ones. By # default it enforces matching and presense by raising exceptions, if ssl.enforce_ttl is set # to 0 it will only log warnings about violations def update_secure_property(msg, secure_property, property, description) req = request_description(msg) unless @config.pluginconf["ssl.enforce_ttl"] == "0" raise "Request #{req} does not have a secure #{description}" unless msg[:body].include?(secure_property) raise "Request #{req} #{description} does not match encrypted #{description} - possible tampering" unless msg[:body][secure_property] == msg[property] else if msg[:body].include?(secure_property) Log.warn("Request #{req} #{description} does not match encrypted #{description} - possible tampering") unless msg[:body][secure_property] == msg[property] else Log.warn("Request #{req} does not have a secure #{description}") unless msg[:body].include?(secure_property) end end msg[property] = msg[:body][secure_property] if msg[:body].include?(secure_property) msg[:body].delete(secure_property) end # Encodes a reply def encodereply(sender, msg, requestid, requestcallerid=nil) serialized = serialize(msg) digest = makehash(serialized) req = create_reply(requestid, sender, serialized) req[:hash] = digest serialize(req) end # Encodes a request msg def encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl=60) req = create_request(requestid, filter, "", @initiated_by, target_agent, target_collective, ttl) ssl_msg = {:ssl_msg => msg, :ssl_ttl => ttl, :ssl_msgtime => req[:msgtime]} serialized = serialize(ssl_msg) digest = makehash(serialized) req[:hash] = digest req[:body] = serialized serialize(req) end # Checks the SSL signature in the request body def validrequest?(req) message = req[:body] signature = req[:hash] Log.debug("Validating request from #{req[:callerid]}") if verify(public_key_file(req[:callerid]), signature, message.to_s) @stats.validated return true else @stats.unvalidated raise(SecurityValidationFailed, "Received an invalid signature in message") end end # sets the caller id to the md5 of the public key def callerid if @initiated_by == :client id = "cert=#{File.basename(client_public_key).gsub(/\.pem$/, '')}" raise "Invalid callerid generated from client public key" unless valid_callerid?(id) else # servers need to set callerid as well, not usually needed but # would be if you're doing registration or auditing or generating # requests for some or other reason id = "cert=#{File.basename(server_public_key).gsub(/\.pem$/, '')}" raise "Invalid callerid generated from server public key" unless valid_callerid?(id) end return id end private # Serializes a message using the configured encoder def serialize(msg) serializer = @config.pluginconf["ssl_serializer"] || "marshal" Log.debug("Serializing using #{serializer}") case serializer when "yaml" return YAML.dump(msg) else return Marshal.dump(msg) end end # De-Serializes a message using the configured encoder def deserialize(msg) serializer = @config.pluginconf["ssl_serializer"] || "marshal" Log.debug("De-Serializing using #{serializer}") case serializer when "yaml" return YAML.load(msg) else return Marshal.load(msg) end end # Figures out where to get our private key def private_key_file if ENV.include?("MCOLLECTIVE_SSL_PRIVATE") return ENV["MCOLLECTIVE_SSL_PRIVATE"] else if @initiated_by == :node return server_private_key else return client_private_key end end end # Figures out the public key to use # # If the node is asking do it based on caller id # If the client is asking just get the node public key def public_key_file(callerid = nil) if @initiated_by == :client return server_public_key else if callerid =~ /cert=([\w\.\-]+)/ cid = $1 if File.exist?("#{client_cert_dir}/#{cid}.pem") return "#{client_cert_dir}/#{cid}.pem" else raise("Could not find a public key for #{cid} in #{client_cert_dir}/#{cid}.pem") end else raise("Caller id is not in the expected format") end end end # Figures out the client private key either from MCOLLECTIVE_SSL_PRIVATE or the # plugin.ssl_client_private config option def client_private_key return ENV["MCOLLECTIVE_SSL_PRIVATE"] if ENV.include?("MCOLLECTIVE_SSL_PRIVATE") raise("No plugin.ssl_client_private configuration option specified") unless @config.pluginconf.include?("ssl_client_private") return @config.pluginconf["ssl_client_private"] end # Figures out the client public key either from MCOLLECTIVE_SSL_PUBLIC or the # plugin.ssl_client_public config option def client_public_key return ENV["MCOLLECTIVE_SSL_PUBLIC"] if ENV.include?("MCOLLECTIVE_SSL_PUBLIC") raise("No plugin.ssl_client_public configuration option specified") unless @config.pluginconf.include?("ssl_client_public") return @config.pluginconf["ssl_client_public"] end # Figures out the server private key from the plugin.ssl_server_private config option def server_private_key raise("No plugin.ssl_server_private configuration option specified") unless @config.pluginconf.include?("ssl_server_private") @config.pluginconf["ssl_server_private"] end # Figures out the server public key from the plugin.ssl_server_public config option def server_public_key raise("No ssl_server_public configuration option specified") unless @config.pluginconf.include?("ssl_server_public") return @config.pluginconf["ssl_server_public"] end # Figures out where to get client public certs from the plugin.ssl_client_cert_dir config option def client_cert_dir raise("No plugin.ssl_client_cert_dir configuration option specified") unless @config.pluginconf.include?("ssl_client_cert_dir") @config.pluginconf["ssl_client_cert_dir"] end # Retrieves the value of plugin.psk and builds a hash with it and the passed body def makehash(body) Log.debug("Creating message hash using #{private_key_file}") sign(private_key_file, body.to_s) end # Code adapted from http://github.com/adamcooke/basicssl # signs a message def sign(key, string) SSL.new(nil, key).sign(string, true) end # verifies a signature def verify(key, signature, string) SSL.new(key).verify_signature(signature, string, true) end def request_description(msg) "%s from %s@%s" % [msg[:requestid], msg[:callerid], msg[:senderid]] end end end end mcollective-2.0.0/plugins/mcollective/security/psk.rb0000644000175000017500000000665111747546503022031 0ustar jonasjonasmodule MCollective module Security # Impliments message authentication using digests and shared keys # # You should configure a psk in the configuration file and all requests # will be validated for authenticity with this. # # Serialization uses Marshal, this is the default security module that is # supported out of the box. # # Validation is as default and is provided by MCollective::Security::Base # # You can configure the caller id being created, this can adjust how you # create authorization plugins. For example you can use a unix group instead # of uid to do authorization. class Psk < Base require 'etc' # Decodes a message by unserializing all the bits etc, it also validates # it as valid using the psk etc def decodemsg(msg) body = Marshal.load(msg.payload) should_process_msg?(msg, body[:requestid]) if validrequest?(body) body[:body] = Marshal.load(body[:body]) return body else nil end end # Encodes a reply def encodereply(sender, msg, requestid, requestcallerid=nil) serialized = Marshal.dump(msg) digest = makehash(serialized) req = create_reply(requestid, sender, serialized) req[:hash] = digest Marshal.dump(req) end # Encodes a request msg def encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl=60) serialized = Marshal.dump(msg) digest = makehash(serialized) req = create_request(requestid, filter, serialized, @initiated_by, target_agent, target_collective, ttl) req[:hash] = digest Marshal.dump(req) end # Checks the md5 hash in the request body against our psk, the request sent for validation # should not have been deserialized already def validrequest?(req) digest = makehash(req[:body]) if digest == req[:hash] @stats.validated return true else @stats.unvalidated raise(SecurityValidationFailed, "Received an invalid signature in message") end end def callerid if @config.pluginconf.include?("psk.callertype") callertype = @config.pluginconf["psk.callertype"].to_sym if @config.pluginconf.include?("psk.callertype") else callertype = :uid end case callertype when :gid id = "gid=#{Process.gid}" when :group raise "Cannot use the 'group' callertype for the PSK security plugin on the Windows platform" if Util.windows? id = "group=#{Etc.getgrgid(Process.gid).name}" when :user id = "user=#{Etc.getlogin}" when :identity id = "identity=#{@config.identity}" else id ="uid=#{Process.uid}" end Log.debug("Setting callerid to #{id} based on callertype=#{callertype}") id end private # Retrieves the value of plugin.psk and builds a hash with it and the passed body def makehash(body) if ENV.include?("MCOLLECTIVE_PSK") psk = ENV["MCOLLECTIVE_PSK"] else raise("No plugin.psk configuration option specified") unless @config.pluginconf.include?("psk") psk = @config.pluginconf["psk"] end Digest::MD5.hexdigest(body.to_s + psk) end end end end mcollective-2.0.0/plugins/mcollective/security/aes_security.rb0000644000175000017500000003216211747546503023727 0ustar jonasjonasmodule MCollective module Security # Impliments a security system that encrypts payloads using AES and secures # the AES encrypted data using RSA public/private key encryption. # # The design goals of this plugin are: # # - Each actor - clients and servers - can have their own set of public and # private keys # - All actors are uniquely and cryptographically identified # - Requests are encrypted using the clients private key and anyone that has # the public key can see the request. Thus an atacker may see the requests # given access to network or machine due to the broadcast nature of mcollective # - The message time and TTL of messages are cryptographically secured making the # ensuring messages can not be replayed with fake TTLs or times # - Replies are encrypted using the calling clients public key. Thus no-one but # the caller can view the contents of replies. # - Servers can all have their own RSA keys, or share one, or reuse keys created # by other PKI using software like Puppet # - Requests from servers - like registration data - can be secured even to external # eaves droppers depending on the level of configuration you are prepared to do # - Given a network where you can ensure third parties are not able to access the # middleware public key distribution can happen automatically # # Configuration Options: # ====================== # # Common Options: # # # Enable this plugin # securityprovider = aes_security # # # Use YAML as serializer # plugin.aes.serializer = yaml # # # Send our public key with every request so servers can learn it # plugin.aes.send_pubkey = 1 # # Clients: # # # The clients public and private keys # plugin.aes.client_private = /home/user/.mcollective.d/user-private.pem # plugin.aes.client_public = /home/user/.mcollective.d/user.pem # # Servers: # # # Where to cache client keys or find manually distributed ones # plugin.aes.client_cert_dir = /etc/mcollective/ssl/clients # # # Cache public keys promiscuously from the network # plugin.aes.learn_pubkeys = 1 # # # Log but accept messages that may have been tampered with # plugin.aes.enforce_ttl = 0 # # # The servers public and private keys # plugin.aes.server_private = /etc/mcollective/ssl/server-private.pem # plugin.aes.server_public = /etc/mcollective/ssl/server-public.pem # class Aes_security body[:sslkey], :data => body[:body]} if @initiated_by == :client body[:body] = deserialize(decrypt(cryptdata, nil)) else body[:body] = deserialize(decrypt(cryptdata, body[:callerid])) # If we got a hash it's possible that this is a message with secure # TTL and message time, attempt to decode that and transform into a # traditional message. # # If it's not a hash it might be a old style message like old discovery # ones that would just be a string so we allow that unaudited but only # if enforce_ttl is disabled. This is primarly to allow a mixed old and # new plugin infrastructure to work if body[:body].is_a?(Hash) update_secure_property(body, :aes_ttl, :ttl, "TTL") update_secure_property(body, :aes_msgtime, :msgtime, "Message Time") body[:body] = body[:body][:aes_msg] if body[:body].include?(:aes_msg) else unless @config.pluginconf["aes.enforce_ttl"] == "0" raise "Message %s is in an unknown or older security protocol, ignoring" % [request_description(body)] end end end return body rescue MsgDoesNotMatchRequestID raise rescue OpenSSL::PKey::RSAError raise MsgDoesNotMatchRequestID, "Could not decrypt message using our key, possibly directed at another client" rescue Exception => e Log.warn("Could not decrypt message from client: #{e.class}: #{e}") raise SecurityValidationFailed, "Could not decrypt message" end # To avoid tampering we turn the origin body into a hash and copy some of the protocol keys # like :ttl and :msg_time into the hash before encrypting it. # # This function compares and updates the unencrypted ones based on the encrypted ones. By # default it enforces matching and presense by raising exceptions, if aes.enforce_ttl is set # to 0 it will only log warnings about violations def update_secure_property(msg, secure_property, property, description) req = request_description(msg) unless @config.pluginconf["aes.enforce_ttl"] == "0" raise "Request #{req} does not have a secure #{description}" unless msg[:body].include?(secure_property) raise "Request #{req} #{description} does not match encrypted #{description} - possible tampering" unless msg[:body][secure_property] == msg[property] else if msg[:body].include?(secure_property) Log.warn("Request #{req} #{description} does not match encrypted #{description} - possible tampering") unless msg[:body][secure_property] == msg[property] else Log.warn("Request #{req} does not have a secure #{description}") unless msg[:body].include?(secure_property) end end msg[property] = msg[:body][secure_property] if msg[:body].include?(secure_property) msg[:body].delete(secure_property) end # Encodes a reply def encodereply(sender, msg, requestid, requestcallerid) crypted = encrypt(serialize(msg), requestcallerid) req = create_reply(requestid, sender, crypted[:data]) req[:sslkey] = crypted[:key] serialize(req) end # Encodes a request msg def encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl=60) req = create_request(requestid, filter, nil, @initiated_by, target_agent, target_collective, ttl) # embed the ttl and msgtime in the crypted data later we will use these in # the decoding of a message to set the message ones from secure sources. this # is to ensure messages are not tampered with to facility replay attacks etc aes_msg = {:aes_msg => msg, :aes_ttl => ttl, :aes_msgtime => req[:msgtime]} crypted = encrypt(serialize(aes_msg), callerid) req[:body] = crypted[:data] req[:sslkey] = crypted[:key] if @config.pluginconf.include?("aes.send_pubkey") && @config.pluginconf["aes.send_pubkey"] == "1" if @initiated_by == :client req[:sslpubkey] = File.read(client_public_key) else req[:sslpubkey] = File.read(server_public_key) end end serialize(req) end # Serializes a message using the configured encoder def serialize(msg) serializer = @config.pluginconf["aes.serializer"] || "marshal" Log.debug("Serializing using #{serializer}") case serializer when "yaml" return YAML.dump(msg) else return Marshal.dump(msg) end end # De-Serializes a message using the configured encoder def deserialize(msg) serializer = @config.pluginconf["aes.serializer"] || "marshal" Log.debug("De-Serializing using #{serializer}") case serializer when "yaml" return YAML.load(msg) else return Marshal.load(msg) end end # sets the caller id to the md5 of the public key def callerid if @initiated_by == :client id = "cert=#{File.basename(client_public_key).gsub(/\.pem$/, '')}" raise "Invalid callerid generated from client public key" unless valid_callerid?(id) else # servers need to set callerid as well, not usually needed but # would be if you're doing registration or auditing or generating # requests for some or other reason id = "cert=#{File.basename(server_public_key).gsub(/\.pem$/, '')}" raise "Invalid callerid generated from server public key" unless valid_callerid?(id) end return id end def encrypt(string, certid) if @initiated_by == :client @ssl ||= SSL.new(client_public_key, client_private_key) Log.debug("Encrypting message using private key") return @ssl.encrypt_with_private(string) else # when the server is initating requests like for registration # then the certid will be our callerid if certid == callerid Log.debug("Encrypting message using private key #{server_private_key}") ssl = SSL.new(server_public_key, server_private_key) return ssl.encrypt_with_private(string) else Log.debug("Encrypting message using public key for #{certid}") ssl = SSL.new(public_key_path_for_client(certid)) return ssl.encrypt_with_public(string) end end end def decrypt(string, certid) if @initiated_by == :client @ssl ||= SSL.new(client_public_key, client_private_key) Log.debug("Decrypting message using private key") return @ssl.decrypt_with_private(string) else Log.debug("Decrypting message using public key for #{certid}") ssl = SSL.new(public_key_path_for_client(certid)) return ssl.decrypt_with_public(string) end end # On servers this will look in the aes.client_cert_dir for public # keys matching the clientid, clientid is expected to be in the format # set by callerid def public_key_path_for_client(clientid) raise "Unknown callerid format in '#{clientid}'" unless clientid.match(/^cert=(.+)$/) clientid = $1 client_cert_dir + "/#{clientid}.pem" end # Figures out the client private key either from MCOLLECTIVE_AES_PRIVATE or the # plugin.aes.client_private config option def client_private_key return ENV["MCOLLECTIVE_AES_PRIVATE"] if ENV.include?("MCOLLECTIVE_AES_PRIVATE") raise("No plugin.aes.client_private configuration option specified") unless @config.pluginconf.include?("aes.client_private") return @config.pluginconf["aes.client_private"] end # Figures out the client public key either from MCOLLECTIVE_AES_PUBLIC or the # plugin.aes.client_public config option def client_public_key return ENV["MCOLLECTIVE_AES_PUBLIC"] if ENV.include?("MCOLLECTIVE_AES_PUBLIC") raise("No plugin.aes.client_public configuration option specified") unless @config.pluginconf.include?("aes.client_public") return @config.pluginconf["aes.client_public"] end # Figures out the server public key from the plugin.aes.server_public config option def server_public_key raise("No aes.server_public configuration option specified") unless @config.pluginconf.include?("aes.server_public") return @config.pluginconf["aes.server_public"] end # Figures out the server private key from the plugin.aes.server_private config option def server_private_key raise("No plugin.aes.server_private configuration option specified") unless @config.pluginconf.include?("aes.server_private") @config.pluginconf["aes.server_private"] end # Figures out where to get client public certs from the plugin.aes.client_cert_dir config option def client_cert_dir raise("No plugin.aes.client_cert_dir configuration option specified") unless @config.pluginconf.include?("aes.client_cert_dir") @config.pluginconf["aes.client_cert_dir"] end def request_description(msg) "%s from %s@%s" % [msg[:requestid], msg[:callerid], msg[:senderid]] end # Takes our cert=foo callerids and return the foo bit else nil def certname_from_callerid(id) if id =~ /^cert=([\w\.\-]+)/ return $1 else Log.warn("Received a callerid in an unexpected format: '#{id}', ignoring") return nil end end end end end mcollective-2.0.0/plugins/mcollective/registration/0000755000175000017500000000000011747546503021542 5ustar jonasjonasmcollective-2.0.0/plugins/mcollective/registration/agentlist.rb0000644000175000017500000000032211747546503024056 0ustar jonasjonasmodule MCollective module Registration # A registration plugin that simply sends in the list of agents we have class Agentlist 0 @package.packagedata.each_with_index do |values, i| if i == 0 puts "%30s%s" % ["Identified Packages : ", values[0]] else puts "%30s%s" % [" ", values[0]] end end end end end end end mcollective-2.0.0/plugins/mcollective/pluginpackager/debpackage_packager.rb0000644000175000017500000001171411747546503026260 0ustar jonasjonasmodule MCollective module PluginPackager class DebpackagePackager require 'erb' attr_accessor :plugin, :current_package, :tmpdir, :verbose, :libdir attr_accessor :workingdir, :preinstall, :postinstall, :current_package_type attr_accessor :current_package_data, :current_package_shortname attr_accessor :current_package_fullname, :build_dir, :signature def initialize(plugin, pluginpath = nil, signature = nil, verbose = false) raise RuntimeError, "package 'debuild' is not installed" unless PluginPackager.build_tool?("debuild") @plugin = plugin @verbose = verbose @libdir = pluginpath || "/usr/share/mcollective/plugins/mcollective/" @signature = signature @tmpdir = "" @builddir = "" @targetdir = "" end def create_packages @plugin.packagedata.each do |type, data| begin @tmpdir = Dir.mktmpdir("mcollective_packager") @current_package_type = type @current_package_data = data @current_package_shortname = "mcollective-#{@plugin.metadata[:name]}-#{@current_package_type}" @current_package_fullname = "mcollective-#{@plugin.metadata[:name]}-#{@current_package_type}" + "_#{@plugin.metadata[:version]}-#{@plugin.iteration}" @build_dir = File.join(@tmpdir, @current_package_fullname) Dir.mkdir @build_dir prepare_tmpdirs data create_package rescue Exception => e raise e ensure cleanup_tmpdirs end end end def create_package begin ["control", "Makefile", "compat", "rules", "copyright", "changelog"].each do |filename| create_file(filename) end create_tar create_install create_preandpost_install FileUtils.cd @build_dir do |f| PluginPackager.do_quietly?(@verbose) do if @signature if @signature.is_a? String PluginPackager.safe_system "debuild -i -k#{@signature}" else PluginPackager.safe_system "debuild -i" end else PluginPackager.safe_system "debuild -i -us -uc" end end end FileUtils.cp(File.join(@tmpdir, "#{@current_package_fullname}_all.deb"), ".") puts "Created package #{@current_package_fullname}" rescue Exception => e raise RuntimeError, "Could not build package - #{e}" end end def create_preandpost_install if @plugin.preinstall raise RuntimeError, "pre-install script '#{@plugin.preinstall}' not found" unless File.exists?(@plugin.preinstall) FileUtils.cp(@plugin.preinstall, File.join(@build_dir, 'debian', "#{@current_package_shortname}.preinst")) end if @plugin.postinstall raise RuntimeError, "post-install script '#{@plugin.postinstall}' not found" unless File.exists?(@plugin.postinstall) FileUtils.cp(@plugin.postinstall, File.join(@build_dir, 'debian', "#{@current_package_shortname}.postinst")) end end def create_install begin File.open(File.join(@build_dir, "debian", "#{@current_package_shortname}.install"), "w") do |f| @current_package_data[:files].each do |filename| f.puts "#{File.join(@libdir, filename).gsub("./", "")} #{File.join(@libdir, File.dirname(filename)).gsub("./","")}" end end rescue Exception => e raise RuntimeError, "Could not create install file - #{e}" end end def create_tar begin PluginPackager.do_quietly?(@verbose) do PluginPackager.safe_system "tar -Pcvzf #{File.join(@tmpdir,"#{@current_package_shortname}_#{@plugin.metadata[:version]}.orig.tar.gz")} #{@build_dir}" end rescue Exception => e raise "Could not create tarball - #{e}" end end def create_file(filename) begin file = ERB.new(File.read(File.join(File.dirname(__FILE__), "templates", "debian", "#{filename}.erb")), nil, "-") File.open(File.join(@build_dir, "debian", filename), "w") do |f| f.puts file.result(binding) end rescue Exception => e raise RuntimeError, "could not create #{filename} file - #{e}" end end def prepare_tmpdirs(data) data[:files].each do |file| @targetdir = File.join(@build_dir, @libdir, File.dirname(File.expand_path(file)).gsub(@plugin.target_path, "")) FileUtils.mkdir_p(@targetdir) unless File.directory? @targetdir FileUtils.cp_r(file, @targetdir) end FileUtils.mkdir_p(File.join(@build_dir, "debian")) end def cleanup_tmpdirs FileUtils.rm_r @tmpdir if File.directory? @tmpdir end end end end mcollective-2.0.0/plugins/mcollective/pluginpackager/rpmpackage_packager.rb0000644000175000017500000000602111747546503026317 0ustar jonasjonasmodule MCollective module PluginPackager class RpmpackagePackager require 'erb' attr_accessor :plugin, :tmpdir, :verbose, :libdir, :workingdir attr_accessor :current_package_type, :current_package_data attr_accessor :current_package_name, :signature def initialize(plugin, pluginpath = nil, signature = nil, verbose = false) raise RuntimeError, "package 'rpm-build' is not installed" unless PluginPackager.build_tool?("rpmbuild") @plugin = plugin @verbose = verbose @libdir = pluginpath || "/usr/libexec/mcollective/mcollective/" @signature = signature end def create_packages @plugin.packagedata.each do |type, data| begin @current_package_type = type @current_package_data = data @current_package_name = "mcollective-#{@plugin.metadata[:name]}-#{@current_package_type}" @tmpdir = Dir.mktmpdir("mcollective_packager") prepare_tmpdirs data create_package type, data rescue Exception => e raise e ensure cleanup_tmpdirs end end end def create_package(type, data) begin make_spec_file PluginPackager.do_quietly?(@verbose) do PluginPackager.safe_system("rpmbuild -bb #{"--quiet" unless verbose} #{"--sign" if @signature} #{File.join(@tmpdir, "SPECS", "#{type}.spec")} --buildroot #{File.join(@tmpdir, "BUILD")}") end FileUtils.cp(File.join(`rpm --eval '%_rpmdir'`.chomp, "noarch", "#{@current_package_name}-#{@plugin.metadata[:version]}-#{@plugin.iteration}.noarch.rpm"), ".") puts "Created package #{@current_package_name}" rescue Exception => e raise RuntimeError, "Could not build package. Reason - #{e}" end end def make_spec_file begin spec_template = ERB.new(File.read(File.join(File.dirname(__FILE__), "templates", "redhat", "rpm_spec.erb")), nil, "-") File.open(File.join(@tmpdir, "SPECS", "#{@current_package_type}.spec"), "w") do |f| f.puts spec_template.result(binding) end rescue Exception => e raise RuntimeError, "Could not create specfile - #{e}" end end def prepare_tmpdirs(data) make_rpm_dirs data[:files].each do |file| targetdir = File.join(@tmpdir, "BUILD", @libdir, File.dirname(File.expand_path(file)).gsub(@plugin.target_path, "")) FileUtils.mkdir_p(targetdir) unless File.directory? targetdir FileUtils.cp_r(file, targetdir) end end def make_rpm_dirs ["BUILD", "SOURCES", "SPECS", "SRPMS", "RPMS"].each do |dir| begin FileUtils.mkdir(File.join(@tmpdir, dir)) rescue Exception => e raise RuntimeError, "Could not create #{dir} directory - #{e}" end end end def cleanup_tmpdirs FileUtils.rm_r @tmpdir if File.directory? @tmpdir end end end end mcollective-2.0.0/plugins/mcollective/pluginpackager/templates/0000755000175000017500000000000011747546503024022 5ustar jonasjonasmcollective-2.0.0/plugins/mcollective/pluginpackager/templates/redhat/0000755000175000017500000000000011747546503025271 5ustar jonasjonasmcollective-2.0.0/plugins/mcollective/pluginpackager/templates/redhat/rpm_spec.erb0000644000175000017500000000164111747546503027575 0ustar jonasjonasSummary: <%= @plugin.metadata[:description] %> Name: <%= @current_package_name%> Version: <%= @plugin.metadata[:version] %> Release: <%= @plugin.iteration %> License: <%= @plugin.metadata[:license] %> URL: <%= @plugin.metadata[:url] %> Vendor: <%= @plugin.vendor %> Packager: <%= @plugin.metadata[:author] %> BuildRoot: %buildroot BuildArch: noarch Group: System Tools <% @current_package_data[:dependencies].each do |dep|%> Requires: <%= dep%> <% end %> %description <%= @plugin.metadata[:description] -%> %prep %build %install %files %defattr(-,root,root,-) <% @current_package_data[:files].each do |file| -%> <%= file.gsub("./", @libdir) %> <% end -%> <% if @plugin.preinstall -%> %pre <%= @plugin.preinstall %> <% end -%> <% if @plugin.postinstall -%> %post <%= @plugin.postinstall%> <% end -%> %changelog * <%= Time.now.strftime("%a %b %d %Y") -%> - <%= @plugin.vendor%> - Built Package <%= @current_package_name-%> mcollective-2.0.0/plugins/mcollective/pluginpackager/templates/debian/0000755000175000017500000000000011747546503025244 5ustar jonasjonasmcollective-2.0.0/plugins/mcollective/pluginpackager/templates/debian/rules.erb0000644000175000017500000000035011747546503027066 0ustar jonasjonas#!/usr/bin/make -f include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/rules/dpatch.mk include /usr/share/cdbs/1/class/makefile.mk DEB_MAKE_INVOKE = $(DEB_MAKE_ENVVARS) make -f debian/Makefile -C $(DEB_BUILDDIR) mcollective-2.0.0/plugins/mcollective/pluginpackager/templates/debian/copyright.erb0000644000175000017500000000032211747546503027743 0ustar jonasjonasThis package was generated by the MCollective plugin packager on <%= Time.now%> Upstream Author: <%= @plugin.metadata[:author] %> <%= @plugin.metadata[:url] %> License: <%= @plugin.metadata[:license] -%> mcollective-2.0.0/plugins/mcollective/pluginpackager/templates/debian/Makefile.erb0000644000175000017500000000004311747546503027450 0ustar jonasjonasDESTDIR= build: clean: install: mcollective-2.0.0/plugins/mcollective/pluginpackager/templates/debian/changelog.erb0000644000175000017500000000050511747546503027665 0ustar jonasjonas<%= @current_package_shortname -%> (<%= @plugin.metadata[:version]-%>-<%= @plugin.iteration-%>) unstable; urgency=low * Automated release for <%= @current_package_shortname-%> by mco plugin packager. -- The Marionette Collective <%= Time.new.strftime('%a, %d %b %Y %H:%M:%S %z') %> mcollective-2.0.0/plugins/mcollective/pluginpackager/templates/debian/control.erb0000644000175000017500000000054611747546503027423 0ustar jonasjonasSource: <%= @current_package_shortname %> Homepage: <%= @plugin.metadata[:url]%> Section: utils Priority: extra Maintainer: <%= @plugin.metadata[:author] %> Standards-Version: 3.8.0 Package: <%= @current_package_shortname %> Architecture: all Depends: <%= @current_package_data[:dependencies].join(", ")%> Description: <%= @plugin.metadata[:description] %> mcollective-2.0.0/plugins/mcollective/pluginpackager/templates/debian/compat.erb0000644000175000017500000000000211747546503027211 0ustar jonasjonas7 mcollective-2.0.0/plugins/mcollective/facts/0000755000175000017500000000000011747546503020130 5ustar jonasjonasmcollective-2.0.0/plugins/mcollective/facts/yaml_facts.rb0000644000175000017500000000277211747546503022607 0ustar jonasjonasmodule MCollective module Facts require 'yaml' # A factsource that reads a hash of facts from a YAML file # # Multiple files can be specified seperated with a : in the # config file, they will be merged with later files overriding # earlier ones in the list. class Yaml_facts e Log.error("Failed to load yaml facts from #{file}: #{e.class}: #{e}") end end facts end # force fact reloads when the mtime on the yaml file change def force_reload? config = Config.instance fact_files = config.pluginconf["yaml"].split(File::PATH_SEPARATOR) fact_files.each do |file| @yaml_file_mtimes[file] ||= File.stat(file).mtime mtime = File.stat(file).mtime if mtime > @yaml_file_mtimes[file] @yaml_file_mtimes[file] = mtime Log.debug("Forcing fact reload due to age of #{file}") return true end end false end end end end mcollective-2.0.0/plugins/mcollective/connector/0000755000175000017500000000000011747546503021022 5ustar jonasjonasmcollective-2.0.0/plugins/mcollective/connector/activemq.rb0000644000175000017500000003703111747546503023164 0ustar jonasjonasrequire 'stomp' module MCollective module Connector # Handles sending and receiving messages over the Stomp protocol for ActiveMQ # servers specifically, we take advantages of ActiveMQ specific features and # enhancements to the Stomp protocol. For best results in a clustered environment # use ActiveMQ 5.5.0 at least. # # This plugin takes an entirely different approach to dealing with ActiveMQ # from the more generic stomp connector. # # - Agents use /topic/..agent # - Replies use temp-topics so they are private and transient. # - Point to Point messages using topics are supported by subscribing to # /topic/.nodes with a selector "mc_identity = 'identity' # # The use of temp-topics for the replies is a huge improvement over the old style. # In the old way all clients got replies for all clients that were active at that # time, this would mean that they would need to decrypt, validate etc in order to # determine if they need to ignore the message, this was computationally expensive # and on large busy networks the messages were being sent all over the show cross # broker boundaries. # # The new way means the messages go point2point back to only whoever requested the # message, they only get their own replies and this is ap private channel that # casual observers cannot just snoop into. # # This plugin supports 1.1.6 and newer of the Stomp rubygem. # # connector = activemq # plugin.activemq.pool.size = 2 # # plugin.activemq.pool.1.host = stomp1.your.net # plugin.activemq.pool.1.port = 6163 # plugin.activemq.pool.1.user = you # plugin.activemq.pool.1.password = secret # plugin.activemq.pool.1.ssl = true # plugin.activemq.pool.1.ssl.cert = /path/to/your.cert # plugin.activemq.pool.1.ssl.key = /path/to/your.key # plugin.activemq.pool.1.ssl.ca = /path/to/your.ca # plugin.activemq.pool.1.ssl.fallback = true # # plugin.activemq.pool.2.host = stomp2.your.net # plugin.activemq.pool.2.port = 6163 # plugin.activemq.pool.2.user = you # plugin.activemq.pool.2.password = secret # plugin.activemq.pool.2.ssl = false # # Using this method you can supply just STOMP_USER and STOMP_PASSWORD. The port will # default to 61613 if not specified. # # The ssl options are only usable in version of the Stomp gem newer than 1.2.2 where these # will imply full SSL validation will be done and you'll only be able to connect to a # ActiveMQ server that has a cert signed by the same CA. If you only set ssl = true # and do not supply the cert, key and ca properties or if you have an older gem it # will fall back to unverified mode only if ssl.fallback is true # # In addition you can set the following options for the rubygem: # # plugin.activemq.initial_reconnect_delay = 0.01 # plugin.activemq.max_reconnect_delay = 30.0 # plugin.activemq.use_exponential_back_off = true # plugin.activemq.back_off_multiplier = 2 # plugin.activemq.max_reconnect_attempts = 0 # plugin.activemq.randomize = false # plugin.activemq.timeout = -1 # # You can set the initial connetion timeout - this is when your stomp server is simply # unreachable - after which it would failover to the next in the pool: # # plugin.activemq.connect_timeout = 5 # # ActiveMQ JMS message priorities can be set: # # plugin.activemq.priority = 4 # class Activemq hosts} # Various STOMP gem options, defaults here matches defaults for 1.1.6 the meaning of # these can be guessed, the documentation isn't clear connection[:initial_reconnect_delay] = Float(get_option("activemq.initial_reconnect_delay", 0.01)) connection[:max_reconnect_delay] = Float(get_option("activemq.max_reconnect_delay", 30.0)) connection[:use_exponential_back_off] = get_bool_option("activemq.use_exponential_back_off", true) connection[:back_off_multiplier] = Integer(get_option("activemq.back_off_multiplier", 2)) connection[:max_reconnect_attempts] = Integer(get_option("activemq.max_reconnect_attempts", 0)) connection[:randomize] = get_bool_option("activemq.randomize", false) connection[:backup] = get_bool_option("activemq.backup", false) connection[:timeout] = Integer(get_option("activemq.timeout", -1)) connection[:connect_timeout] = Integer(get_option("activemq.connect_timeout", 5)) connection[:reliable] = true connection[:logger] = EventLogger.new @connection = connector.new(connection) rescue Exception => e raise("Could not connect to ActiveMQ Server: #{e}") end end # Sets the SSL paramaters for a specific connection def ssl_parameters(poolnum, fallback) params = {:cert_file => get_option("activemq.pool.#{poolnum}.ssl.cert", false), :key_file => get_option("activemq.pool.#{poolnum}.ssl.key", false), :ts_files => get_option("activemq.pool.#{poolnum}.ssl.ca", false)} raise "cert, key and ca has to be supplied for verified SSL mode" unless params[:cert_file] && params[:key_file] && params[:ts_files] raise "Cannot find certificate file #{params[:cert_file]}" unless File.exist?(params[:cert_file]) raise "Cannot find key file #{params[:key_file]}" unless File.exist?(params[:key_file]) params[:ts_files].split(",").each do |ca| raise "Cannot find CA file #{ca}" unless File.exist?(ca) end begin Stomp::SSLParams.new(params) rescue NameError raise "Stomp gem >= 1.2.2 is needed" end rescue Exception => e if fallback Log.warn("Failed to set full SSL verified mode, falling back to unverified: #{e.class}: #{e}") return true else Log.error("Failed to set full SSL verified mode: #{e.class}: #{e}") raise(e) end end # Receives a message from the ActiveMQ connection def receive Log.debug("Waiting for a message from ActiveMQ") # When the Stomp library > 1.2.0 is mid reconnecting due to its reliable connection # handling it sets the connection to closed. If we happen to be receiving at just # that time we will get an exception warning about the closed connection so handling # that here with a sleep and a retry. begin msg = @connection.receive rescue ::Stomp::Error::NoCurrentConnection sleep 1 retry end Message.new(msg.body, msg, :base64 => @base64, :headers => msg.headers) end # Sends a message to the ActiveMQ connection def publish(msg) msg.base64_encode! if @base64 target = target_for(msg) if msg.type == :direct_request msg.discovered_hosts.each do |node| target[:headers] = headers_for(msg, node) Log.debug("Sending a direct message to ActiveMQ target '#{target[:name]}' with headers '#{target[:headers].inspect}'") @connection.publish(target[:name], msg.payload, target[:headers]) end else target[:headers].merge!(headers_for(msg)) Log.debug("Sending a broadcast message to ActiveMQ target '#{target[:name]}' with headers '#{target[:headers].inspect}'") @connection.publish(target[:name], msg.payload, target[:headers]) end end # Subscribe to a topic or queue def subscribe(agent, type, collective) source = make_target(agent, type, collective) unless @subscriptions.include?(source[:id]) Log.debug("Subscribing to #{source[:name]} with headers #{source[:headers].inspect.chomp}") @connection.subscribe(source[:name], source[:headers], source[:id]) @subscriptions << source[:id] end rescue ::Stomp::Error::DuplicateSubscription Log.error("Received subscription request for #{source.inspect.chomp} but already had a matching subscription, ignoring") end # Subscribe to a topic or queue def unsubscribe(agent, type, collective) source = make_target(agent, type, collective) Log.debug("Unsubscribing from #{source[:name]}") @connection.unsubscribe(source[:name], source[:headers], source[:id]) @subscriptions.delete(source[:id]) end def target_for(msg) if msg.type == :reply target = {:name => msg.request.headers["reply-to"], :headers => {}} elsif [:request, :direct_request].include?(msg.type) target = make_target(msg.agent, msg.type, msg.collective) else raise "Don't now how to create a target for message type #{msg.type}" end return target end # Disconnects from the ActiveMQ connection def disconnect Log.debug("Disconnecting from ActiveMQ") @connection.disconnect end def headers_for(msg, identity=nil) headers = {} headers = {"priority" => @msgpriority} if @msgpriority > 0 if [:request, :direct_request].include?(msg.type) target = make_target(msg.agent, :reply, msg.collective) if msg.reply_to headers["reply-to"] = msg.reply_to else headers["reply-to"] = target[:name] end headers["mc_identity"] = identity if msg.type == :direct_request end return headers end def make_target(agent, type, collective) raise("Unknown target type #{type}") unless [:directed, :broadcast, :reply, :request, :direct_request].include?(type) raise("Unknown collective '#{collective}' known collectives are '#{@config.collectives.join ', '}'") unless @config.collectives.include?(collective) target = {:name => nil, :headers => {}} case type when :reply target[:name] = ["/queue/" + collective, :reply, "#{Config.instance.identity}_#{$$}"].join(".") when :broadcast target[:name] = ["/topic/" + collective, agent, :agent].join(".") when :request target[:name] = ["/topic/" + collective, agent, :agent].join(".") when :direct_request target[:name] = ["/queue/" + collective, :nodes].join(".") when :directed target[:name] = ["/queue/" + collective, :nodes].join(".") target[:headers]["selector"] = "mc_identity = '#{@config.identity}'" target[:id] = "directed_to_identity" end target[:id] = target[:name] unless target[:id] target end # looks in the environment first then in the config file # for a specific option, accepts an optional default. # # raises an exception when it cant find a value anywhere def get_env_or_option(env, opt, default=nil) return ENV[env] if ENV.include?(env) return @config.pluginconf[opt] if @config.pluginconf.include?(opt) return default if default raise("No #{env} environment or plugin.#{opt} configuration option given") end # looks for a config option, accepts an optional default # # raises an exception when it cant find a value anywhere def get_option(opt, default=nil) return @config.pluginconf[opt] if @config.pluginconf.include?(opt) return default unless default.nil? raise("No plugin.#{opt} configuration option given") end # gets a boolean option from the config, supports y/n/true/false/1/0 def get_bool_option(opt, default) return default unless @config.pluginconf.include?(opt) val = @config.pluginconf[opt] if val =~ /^1|yes|true/ return true elsif val =~ /^0|no|false/ return false else return default end end end end end # vi:tabstop=4:expandtab:ai mcollective-2.0.0/plugins/mcollective/connector/stomp.rb0000644000175000017500000003052111747546503022512 0ustar jonasjonasrequire 'stomp' module MCollective module Connector # Handles sending and receiving messages over the Stomp protocol # # This plugin supports version 1.1 or 1.1.6 and newer of the Stomp rubygem # the versions between those had multi threading issues. # # For all versions you can configure it as follows: # # connector = stomp # plugin.stomp.host = stomp.your.net # plugin.stomp.port = 6163 # plugin.stomp.user = you # plugin.stomp.password = secret # # All of these can be overriden per user using environment variables: # # STOMP_SERVER, STOMP_PORT, STOMP_USER, STOMP_PASSWORD # # Version 1.1.6 onward support supplying multiple connections and it will # do failover between these servers, you can configure it as follows: # # connector = stomp # plugin.stomp.pool.size = 2 # # plugin.stomp.pool.host1 = stomp1.your.net # plugin.stomp.pool.port1 = 6163 # plugin.stomp.pool.user1 = you # plugin.stomp.pool.password1 = secret # plugin.stomp.pool.ssl1 = true # # plugin.stomp.pool.host2 = stomp2.your.net # plugin.stomp.pool.port2 = 6163 # plugin.stomp.pool.user2 = you # plugin.stomp.pool.password2 = secret # plugin.stomp.pool.ssl2 = false # # Using this method you can supply just STOMP_USER and STOMP_PASSWORD # you have to supply the hostname for each pool member in the config. # The port will default to 6163 if not specified. # # In addition you can set the following options but only when using # pooled configuration: # # plugin.stomp.pool.initial_reconnect_delay = 0.01 # plugin.stomp.pool.max_reconnect_delay = 30.0 # plugin.stomp.pool.use_exponential_back_off = true # plugin.stomp.pool.back_off_multiplier = 2 # plugin.stomp.pool.max_reconnect_attempts = 0 # plugin.stomp.pool.randomize = false # plugin.stomp.pool.timeout = -1 # # For versions of ActiveMQ that supports message priorities # you can set a priority, this will cause a "priority" header # to be emitted if present: # # plugin.stomp.priority = 4 # class Stomp hosts} # Various STOMP gem options, defaults here matches defaults for 1.1.6 the meaning of # these can be guessed, the documentation isn't clear connection[:initial_reconnect_delay] = get_option("stomp.pool.initial_reconnect_delay", 0.01).to_f connection[:max_reconnect_delay] = get_option("stomp.pool.max_reconnect_delay", 30.0).to_f connection[:use_exponential_back_off] = get_bool_option("stomp.pool.use_exponential_back_off", true) connection[:back_off_multiplier] = get_bool_option("stomp.pool.back_off_multiplier", 2).to_i connection[:max_reconnect_attempts] = get_option("stomp.pool.max_reconnect_attempts", 0).to_i connection[:randomize] = get_bool_option("stomp.pool.randomize", false) connection[:backup] = get_bool_option("stomp.pool.backup", false) connection[:timeout] = get_option("stomp.pool.timeout", -1).to_i connection[:reliable] = true connection[:logger] = EventLogger.new @connection = connector.new(connection) end rescue Exception => e raise("Could not connect to Stomp Server: #{e}") end end # Receives a message from the Stomp connection def receive Log.debug("Waiting for a message from Stomp") # When the Stomp library > 1.2.0 is mid reconnecting due to its reliable connection # handling it sets the connection to closed. If we happen to be receiving at just # that time we will get an exception warning about the closed connection so handling # that here with a sleep and a retry. begin msg = @connection.receive rescue ::Stomp::Error::NoCurrentConnection sleep 1 retry end Message.new(msg.body, msg, :base64 => @base64, :headers => msg.headers) end # Sends a message to the Stomp connection def publish(msg) msg.base64_encode! if @base64 raise "Cannot set specific reply to targets with the STOMP plugin" if msg.reply_to if msg.type == :direct_request msg.discovered_hosts.each do |node| target = make_target(msg.agent, msg.type, msg.collective, node) Log.debug("Sending a direct message to STOMP target '#{target}'") publish_msg(target, msg.payload) end else target = make_target(msg.agent, msg.type, msg.collective) Log.debug("Sending a broadcast message to STOMP target '#{target}'") publish_msg(target, msg.payload) end end # Subscribe to a topic or queue def subscribe(agent, type, collective) source = make_target(agent, type, collective) unless @subscriptions.include?(source) Log.debug("Subscribing to #{source}") @connection.subscribe(source) @subscriptions << source end rescue ::Stomp::Error::DuplicateSubscription Log.debug("Received subscription for #{source[:name]} but already had a subscription, ignoring") end # Actually sends the message to the middleware def publish_msg(target, msg) # deal with deprecation warnings in newer stomp gems if @connection.respond_to?("publish") @connection.publish(target, msg, msgheaders) else @connection.send(target, msg, msgheaders) end end # Subscribe to a topic or queue def unsubscribe(agent, type, collective) source = make_target(agent, type, collective) Log.debug("Unsubscribing from #{source}") @connection.unsubscribe(source) @subscriptions.delete(source) end # Disconnects from the Stomp connection def disconnect Log.debug("Disconnecting from Stomp") @connection.disconnect end def msgheaders headers = {} headers = {"priority" => @msgpriority} if @msgpriority > 0 return headers end # looks in the environment first then in the config file # for a specific option, accepts an optional default. # # raises an exception when it cant find a value anywhere def get_env_or_option(env, opt, default=nil) return ENV[env] if ENV.include?(env) return @config.pluginconf[opt] if @config.pluginconf.include?(opt) return default if default raise("No #{env} environment or plugin.#{opt} configuration option given") end # looks for a config option, accepts an optional default # # raises an exception when it cant find a value anywhere def get_option(opt, default=nil) return @config.pluginconf[opt] if @config.pluginconf.include?(opt) return default if default raise("No plugin.#{opt} configuration option given") end # gets a boolean option from the config, supports y/n/true/false/1/0 def get_bool_option(opt, default) return default unless @config.pluginconf.include?(opt) val = @config.pluginconf[opt] if val =~ /^1|yes|true/ return true elsif val =~ /^0|no|false/ return false else return default end end def make_target(agent, type, collective, target_node=nil) raise("Unknown target type #{type}") unless [:directed, :broadcast, :reply, :request, :direct_request].include?(type) raise("Unknown collective '#{collective}' known collectives are '#{@config.collectives.join ', '}'") unless @config.collectives.include?(collective) prefix = @config.topicprefix case type when :reply suffix = :reply when :broadcast suffix = :command when :request suffix = :command when :direct_request agent = nil prefix = @config.queueprefix suffix = Digest::MD5.hexdigest(target_node) when :directed agent = nil prefix = @config.queueprefix # use a md5 since hostnames might have illegal characters that # the middleware dont understand suffix = Digest::MD5.hexdigest(@config.identity) end ["#{prefix}#{collective}", agent, suffix].compact.join(@config.topicsep) end end end end mcollective-2.0.0/plugins/mcollective/audit/0000755000175000017500000000000011747546503020136 5ustar jonasjonasmcollective-2.0.0/plugins/mcollective/audit/logfile.rb0000644000175000017500000000152711747546503022111 0ustar jonasjonasmodule MCollective module RPC # An audit plugin that just logs to a file # # You can configure which file it logs to with the setting # # plugin.rpcaudit.logfile # class Logfile