scout 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +4 -0
- data/CHANGELOG +60 -0
- data/COPYING +340 -0
- data/INSTALL +18 -0
- data/LICENSE +6 -0
- data/README +34 -0
- data/Rakefile +120 -0
- data/TODO +6 -0
- data/bin/scout +200 -0
- data/lib/scout.rb +8 -0
- data/lib/scout/plugin.rb +25 -0
- data/lib/scout/server.rb +285 -0
- data/setup.rb +1360 -0
- data/test/scout_test.rb +91 -0
- metadata +82 -0
data/Rakefile
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require "rake/rdoctask"
|
2
|
+
require "rake/testtask"
|
3
|
+
require "rake/gempackagetask"
|
4
|
+
require "rake/contrib/rubyforgepublisher"
|
5
|
+
require "net/ssh"
|
6
|
+
|
7
|
+
require "rubygems"
|
8
|
+
require "rubyforge"
|
9
|
+
|
10
|
+
dir = File.dirname(__FILE__)
|
11
|
+
lib = File.join(dir, "lib", "scout.rb")
|
12
|
+
version = File.read(lib)[/^\s*VERSION\s*=\s*(['"])(\d\.\d\.\d)\1/, 2]
|
13
|
+
history = File.read("CHANGELOG").split(/^(===.*)/)
|
14
|
+
changes ||= history[0..2].join.strip
|
15
|
+
|
16
|
+
need_tar = true
|
17
|
+
need_zip = true
|
18
|
+
|
19
|
+
task :default => [:test]
|
20
|
+
|
21
|
+
Rake::TestTask.new do |test|
|
22
|
+
test.libs << "test"
|
23
|
+
test.test_files = [ "test/scout_test.rb" ]
|
24
|
+
test.verbose = true
|
25
|
+
end
|
26
|
+
|
27
|
+
Rake::RDocTask.new do |rdoc|
|
28
|
+
rdoc.main = "README"
|
29
|
+
rdoc.rdoc_dir = "doc/html"
|
30
|
+
rdoc.title = "Scout Client Documentation"
|
31
|
+
rdoc.rdoc_files.include( "README", "INSTALL",
|
32
|
+
"TODO", "CHANGELOG",
|
33
|
+
"AUTHORS", "COPYING",
|
34
|
+
"LICENSE", "lib/" )
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Upload current documentation to Scout Gem Server"
|
38
|
+
task :upload_docs => [:rdoc] do
|
39
|
+
sh "scp -r doc/html/* " +
|
40
|
+
"deploy@gems.scoutapp.com:/var/www/gems/docs"
|
41
|
+
end
|
42
|
+
|
43
|
+
spec = Gem::Specification.new do |spec|
|
44
|
+
spec.name = "scout"
|
45
|
+
spec.version = version
|
46
|
+
|
47
|
+
spec.platform = Gem::Platform::RUBY
|
48
|
+
spec.summary = "Scout makes monitoring and reporting on your web applications as flexible and simple as possible."
|
49
|
+
|
50
|
+
# TODO: test suite
|
51
|
+
# spec.test_suite_file = "test/ts_all.rb"
|
52
|
+
spec.files = Dir.glob("{lib,test,examples}/**/*.rb").
|
53
|
+
reject { |item| item.include?(".svn") } +
|
54
|
+
Dir.glob("{test,examples}/**/*.csv").
|
55
|
+
reject { |item| item.include?(".svn") } +
|
56
|
+
["Rakefile", "setup.rb"]
|
57
|
+
spec.executables = ["scout"]
|
58
|
+
|
59
|
+
spec.has_rdoc = true
|
60
|
+
spec.extra_rdoc_files = %w[ AUTHORS COPYING README INSTALL TODO CHANGELOG
|
61
|
+
LICENSE ]
|
62
|
+
spec.rdoc_options << "--title" << "Scout Client Documentation" <<
|
63
|
+
"--main" << "README"
|
64
|
+
|
65
|
+
spec.require_path = "lib"
|
66
|
+
|
67
|
+
spec.add_dependency "elif"
|
68
|
+
# spec.add_dependency "hpricot", "=0.6"
|
69
|
+
|
70
|
+
spec.author = "Highgroove Studios"
|
71
|
+
spec.email = "scout@highgroove.com"
|
72
|
+
spec.rubyforge_project = "scout"
|
73
|
+
spec.homepage = "http://scoutapp.com"
|
74
|
+
spec.description = <<END_DESC
|
75
|
+
Scout makes monitoring and reporting on your web applications as flexible and simple as possible.
|
76
|
+
|
77
|
+
Scout is a product of Highgroove Studios.
|
78
|
+
END_DESC
|
79
|
+
end
|
80
|
+
|
81
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
82
|
+
pkg.need_zip = need_tar
|
83
|
+
pkg.need_tar = need_zip
|
84
|
+
end
|
85
|
+
|
86
|
+
desc "Publish Gem to Scout Gem Server"
|
87
|
+
task :publish => [:package] do
|
88
|
+
pkg = "pkg/#{spec.name}-#{spec.version}"
|
89
|
+
|
90
|
+
if $DEBUG then
|
91
|
+
puts "release_id = rf.add_release #{spec.rubyforge_project.inspect}, #{spec.name.inspect}, #{spec.version.inspect}, \"#{pkg}.tgz\""
|
92
|
+
puts "rf.add_file #{spec.rubyforge_project.inspect}, #{spec.name.inspect}, release_id, \"#{pkg}.gem\""
|
93
|
+
end
|
94
|
+
|
95
|
+
puts "Publishing on RubyForge"
|
96
|
+
rf = RubyForge.new
|
97
|
+
puts "Logging in"
|
98
|
+
rf.login
|
99
|
+
|
100
|
+
c = rf.userconfig
|
101
|
+
puts rf.inspect
|
102
|
+
c["release_notes"] = spec.description if spec.description
|
103
|
+
c["release_changes"] = changes if changes
|
104
|
+
c["preformatted"] = true
|
105
|
+
|
106
|
+
files = [(need_tar ? "#{pkg}.tgz" : nil),
|
107
|
+
(need_zip ? "#{pkg}.zip" : nil),
|
108
|
+
"#{pkg}.gem"].compact
|
109
|
+
|
110
|
+
puts "Releasing #{spec.name} v. #{spec.version}"
|
111
|
+
rf.add_release spec.rubyforge_project, spec.name, spec.version, *files
|
112
|
+
|
113
|
+
puts "Publishing on Scout Server"
|
114
|
+
sh "scp -r pkg/*.gem " +
|
115
|
+
"deploy@gems.scoutapp.com:/var/www/gems/gems"
|
116
|
+
ssh = Net::SSH.start('gems.scoutapp.com','deploy')
|
117
|
+
ssh_shell = ssh.shell.sync
|
118
|
+
ssh_out = ssh_shell.send_command "/usr/bin/index_gem_repository.rb -d /var/www/gems"
|
119
|
+
puts "Published, and updated gem server." if ssh_out.stdout.empty? && !ssh_out.stderr
|
120
|
+
end
|
data/TODO
ADDED
data/bin/scout
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$VERBOSE = true # -w
|
4
|
+
$KCODE = "u" # -Ku
|
5
|
+
|
6
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), *%w[.. lib])
|
7
|
+
require "scout"
|
8
|
+
require "optparse"
|
9
|
+
require "logger"
|
10
|
+
require "fileutils"
|
11
|
+
require "pp"
|
12
|
+
|
13
|
+
CONFIG_DIR = File.join((File.expand_path("~") rescue "/"), ".scout")
|
14
|
+
USER = ENV["USER"] || ENV["USERNAME"] || "root"
|
15
|
+
|
16
|
+
options = { :server => "https://scoutapp.com/",
|
17
|
+
:history => File.join(CONFIG_DIR , "client_history.yaml"),
|
18
|
+
:verbose => false,
|
19
|
+
:level => "info" }
|
20
|
+
|
21
|
+
ARGV.options do |opts|
|
22
|
+
opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} [OPTIONS] CLIENT_KEY"
|
23
|
+
|
24
|
+
opts.separator ""
|
25
|
+
opts.separator "CLIENT_KEY is the indentification key assigned to this " +
|
26
|
+
"client by the server."
|
27
|
+
opts.separator ""
|
28
|
+
opts.separator "Note: This client is meant to be installed and invoked " +
|
29
|
+
"through cron or any other scheduler."
|
30
|
+
opts.separator ""
|
31
|
+
opts.separator "Specific Options:"
|
32
|
+
|
33
|
+
opts.on( "-s", "--server SERVER", String,
|
34
|
+
"The URL for the server this client reports to." ) do |url|
|
35
|
+
options[:server] = url
|
36
|
+
end
|
37
|
+
opts.on( "-p", "--plugin PLUGIN", String,
|
38
|
+
"The path to a plugin to run locally. " +
|
39
|
+
"Useful for Testing." ) do |plugin|
|
40
|
+
options[:plugin] = plugin
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.separator ""
|
44
|
+
|
45
|
+
opts.on( "-d", "--data DATA", String,
|
46
|
+
"The data file used to track the history of executions." ) do |file|
|
47
|
+
options[:history] = file
|
48
|
+
end
|
49
|
+
opts.on( "-l", "--level LEVEL", Logger::SEV_LABEL.map { |l| l.downcase },
|
50
|
+
"The level of logging to report." ) do |level|
|
51
|
+
options[:level] = level
|
52
|
+
end
|
53
|
+
opts.on( "-o", "--plugin-options PLUGIN_OPTIONS", String,
|
54
|
+
"The options YAML file to pass to the plugin." ) do |plugin_options|
|
55
|
+
options[:plugin_options] = plugin_options
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.separator "Common Options:"
|
59
|
+
|
60
|
+
opts.on( "-h", "--help",
|
61
|
+
"Show this message." ) do
|
62
|
+
puts opts
|
63
|
+
exit
|
64
|
+
end
|
65
|
+
opts.on( "-v", "--[no-]verbose",
|
66
|
+
"Turn on logging to STDOUT" ) do |bool|
|
67
|
+
options[:verbose] = bool
|
68
|
+
end
|
69
|
+
|
70
|
+
begin
|
71
|
+
opts.parse!
|
72
|
+
options[:client_key] = ARGV.shift if ARGV.size == 1
|
73
|
+
rescue
|
74
|
+
puts opts
|
75
|
+
exit
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
log = Logger.new($stdout)
|
80
|
+
log.datetime_format = "%Y-%m-%d %H:%M "
|
81
|
+
log.level = Logger.const_get(options[:level].upcase) \
|
82
|
+
rescue Logger::INFO
|
83
|
+
|
84
|
+
real_config_dir = File.dirname(options[:history])
|
85
|
+
FileUtils.mkdir_p(real_config_dir) # ensure dir exists
|
86
|
+
|
87
|
+
# make sure only one copy is ever running at a time
|
88
|
+
pid_file = File.join(real_config_dir, "scout_client_pid.txt")
|
89
|
+
begin
|
90
|
+
File.open(pid_file, File::CREAT|File::EXCL|File::WRONLY) { |pid| pid.puts $$ }
|
91
|
+
at_exit do
|
92
|
+
begin
|
93
|
+
File.unlink(pid_file)
|
94
|
+
rescue
|
95
|
+
log.error "Unable to unlink pid file: #{$!.message}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
rescue
|
99
|
+
pid = File.read(pid_file).strip.to_i rescue "unknown"
|
100
|
+
running = true
|
101
|
+
begin
|
102
|
+
Process.kill(0, pid)
|
103
|
+
rescue Errno::ESRCH
|
104
|
+
running = false
|
105
|
+
rescue
|
106
|
+
# do nothing, we didn't have permission to the running process
|
107
|
+
end
|
108
|
+
if running
|
109
|
+
log.warn "Process #{pid} was already running"
|
110
|
+
exit
|
111
|
+
else
|
112
|
+
log.info "Stale PID file found. Clearing it and reloading..."
|
113
|
+
File.unlink(pid_file)
|
114
|
+
retry
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
if [:client_key, :plugin].all? { |o| options[o].nil? } and
|
119
|
+
$stdin.tty? # install wizard
|
120
|
+
puts <<-END_INTRO.gsub(/^ {2}/, "")
|
121
|
+
== Scout Installation Wizard ==
|
122
|
+
|
123
|
+
You need the Client Key displayed in the Client Settings
|
124
|
+
tab. It looks like:
|
125
|
+
|
126
|
+
6ecad322-0d17-4cb8-9b2c-a12c4541853f
|
127
|
+
|
128
|
+
Enter the Client Key:
|
129
|
+
END_INTRO
|
130
|
+
options[:client_key] = gets.to_s.chomp!
|
131
|
+
|
132
|
+
# puts "Attempting to contact the server..."
|
133
|
+
begin
|
134
|
+
Scout::Server.new( options[:server],
|
135
|
+
options[:client_key],
|
136
|
+
options[:history],
|
137
|
+
options[:verbose] ? log : nil ) { |server| server.test }
|
138
|
+
|
139
|
+
puts <<-END_SUCCESS.gsub(/^ {4}/, "")
|
140
|
+
|
141
|
+
Success!
|
142
|
+
|
143
|
+
******* NOW, INSTALL IN CRONTAB *******
|
144
|
+
|
145
|
+
*/10 * * * * #{USER} #{File.expand_path($PROGRAM_NAME)} #{options[:client_key]}
|
146
|
+
|
147
|
+
******* END CRONTAB SAMPLE *******
|
148
|
+
|
149
|
+
For help setting up Scout with crontab, please visit:
|
150
|
+
|
151
|
+
http://scoutapp.com/help#cron
|
152
|
+
|
153
|
+
END_SUCCESS
|
154
|
+
rescue SystemExit
|
155
|
+
puts <<-END_ERROR.gsub(/^ {4}/, "")
|
156
|
+
|
157
|
+
Could not contact server. The client key may be incorrect. For more help,
|
158
|
+
please visit:
|
159
|
+
|
160
|
+
http://scoutapp.com/help
|
161
|
+
|
162
|
+
END_ERROR
|
163
|
+
end
|
164
|
+
elsif options[:plugin] # local plugin
|
165
|
+
# read the plugin_code from the file specified
|
166
|
+
plugin_code = File.read(options[:plugin])
|
167
|
+
|
168
|
+
plugin_options = if options[:plugin_options].to_s[0..0] == "{"
|
169
|
+
eval(options[:plugin_options]) # options from command-line
|
170
|
+
elsif options[:plugin_options]
|
171
|
+
#
|
172
|
+
# read the plugin_options from the YAML file specified,
|
173
|
+
# parse each option and use the default value specified
|
174
|
+
# in the options as the value to be passed to the test plugin
|
175
|
+
#
|
176
|
+
Hash[ *File.open(options[:plugin_options]) { |f| YAML.load(f) }["options"].
|
177
|
+
map { |name, details| [name, details["default"]] }.flatten ]
|
178
|
+
else
|
179
|
+
Hash.new
|
180
|
+
end
|
181
|
+
|
182
|
+
Scout::Server.new( nil,
|
183
|
+
options[:client_key],
|
184
|
+
options[:history],
|
185
|
+
options[:verbose] ? log : nil ) do |server|
|
186
|
+
pp server.process_plugin( { :interval => 0,
|
187
|
+
:plugin_id => 1,
|
188
|
+
:name => "Local Plugin",
|
189
|
+
:code => plugin_code,
|
190
|
+
:options => plugin_options,
|
191
|
+
:path => options[:plugin] } )
|
192
|
+
end
|
193
|
+
else # normal run
|
194
|
+
Scout::Server.new( options[:server],
|
195
|
+
options[:client_key],
|
196
|
+
options[:history],
|
197
|
+
options[:verbose] ? log : nil ) do |server|
|
198
|
+
server.run_plugins_by_plan
|
199
|
+
end
|
200
|
+
end
|
data/lib/scout.rb
ADDED
data/lib/scout/plugin.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby -wKU
|
2
|
+
|
3
|
+
module Scout
|
4
|
+
class Plugin
|
5
|
+
class << self
|
6
|
+
attr_reader :last_defined
|
7
|
+
|
8
|
+
def inherited(new_plugin)
|
9
|
+
@last_defined = new_plugin
|
10
|
+
end
|
11
|
+
|
12
|
+
def load(last_run, memory, options)
|
13
|
+
new(last_run, memory, options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates a new Scout Plugin to run.
|
18
|
+
#
|
19
|
+
def initialize(last_run, memory, options)
|
20
|
+
@last_run = last_run
|
21
|
+
@memory = memory
|
22
|
+
@options = options
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/scout/server.rb
ADDED
@@ -0,0 +1,285 @@
|
|
1
|
+
#!/usr/bin/env ruby -wKU
|
2
|
+
|
3
|
+
require "net/https"
|
4
|
+
require "uri"
|
5
|
+
require "yaml"
|
6
|
+
require "timeout"
|
7
|
+
|
8
|
+
module Scout
|
9
|
+
class Server
|
10
|
+
# A new class for plugin Timeout errors.
|
11
|
+
class PluginTimeoutError < RuntimeError; end
|
12
|
+
|
13
|
+
# The default URLS are used to communicate with the Scout Server.
|
14
|
+
URLS = { :plan => "/clients/CLIENT_KEY/plugins.scout?version=CLIENT_VERSION",
|
15
|
+
:report => "/clients/CLIENT_KEY/plugins/PLUGIN_ID/reports.scout?version=CLIENT_VERSION",
|
16
|
+
:error => "/clients/CLIENT_KEY/plugins/PLUGIN_ID/errors.scout?version=CLIENT_VERSION",
|
17
|
+
:alert => "/clients/CLIENT_KEY/plugins/PLUGIN_ID/alerts.scout?version=CLIENT_VERSION" }
|
18
|
+
|
19
|
+
#
|
20
|
+
# A plugin cannot take more than PLUGIN_TIMEOUT seconds to execute,
|
21
|
+
# otherwise, a timeout error is generated.
|
22
|
+
#
|
23
|
+
PLUGIN_TIMEOUT = 60
|
24
|
+
|
25
|
+
# Creates a new Scout Server connection.
|
26
|
+
def initialize(server, client_key, history_file, logger = nil)
|
27
|
+
@server = server
|
28
|
+
@client_key = client_key
|
29
|
+
@history_file = history_file
|
30
|
+
@history = Hash.new
|
31
|
+
@logger = logger
|
32
|
+
|
33
|
+
if block_given?
|
34
|
+
load_history
|
35
|
+
yield self
|
36
|
+
save_history
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Loads the history file from disk. If the file does not exist,
|
42
|
+
# it creates one.
|
43
|
+
#
|
44
|
+
def load_history
|
45
|
+
unless File.exist? @history_file
|
46
|
+
debug "Creating empty history file..."
|
47
|
+
File.open(@history_file, "w") do |file|
|
48
|
+
YAML.dump({"last_runs" => Hash.new, "memory" => Hash.new}, file)
|
49
|
+
end
|
50
|
+
info "History file created."
|
51
|
+
end
|
52
|
+
debug "Loading history file..."
|
53
|
+
@history = File.open(@history_file) { |file| YAML.load(file) }
|
54
|
+
info "History file loaded."
|
55
|
+
end
|
56
|
+
|
57
|
+
# Saves the history file to disk.
|
58
|
+
def save_history
|
59
|
+
debug "Saving history file..."
|
60
|
+
File.open(@history_file, "w") { |file| YAML.dump(@history, file) }
|
61
|
+
info "History file saved."
|
62
|
+
end
|
63
|
+
|
64
|
+
# Runs all plugins from a given plan. Calls process_plugin on each plugin.
|
65
|
+
def run_plugins_by_plan
|
66
|
+
plan do |plugin|
|
67
|
+
process_plugin(plugin)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# This is the heart of Scout.
|
73
|
+
#
|
74
|
+
# First, it determines if a plugin is past interval and needs to be run.
|
75
|
+
# If it is, it simply evals the code, compiling it.
|
76
|
+
# It then loads the plugin and runs it with a PLUGIN_TIMEOUT time limit.
|
77
|
+
# The plugin generates data, alerts, and errors. In addition, it will
|
78
|
+
# set memory and last_run information in the history file.
|
79
|
+
#
|
80
|
+
def process_plugin(plugin)
|
81
|
+
info "Processing the #{plugin[:name]} plugin:"
|
82
|
+
last_run = @history["last_runs"][plugin[:name]]
|
83
|
+
memory = @history["memory"][plugin[:name]]
|
84
|
+
run_time = Time.now
|
85
|
+
if last_run.nil? or run_time > last_run + plugin[:interval]
|
86
|
+
debug "Plugin is past interval and needs to be run. " +
|
87
|
+
"(last run: #{last_run || 'nil'})"
|
88
|
+
debug "Compiling plugin..."
|
89
|
+
begin
|
90
|
+
eval(plugin[:code], TOPLEVEL_BINDING, plugin[:path] || plugin[:name])
|
91
|
+
info "Plugin compiled."
|
92
|
+
rescue Exception
|
93
|
+
error "Plugin would not compile: #{$!.message}"
|
94
|
+
return
|
95
|
+
end
|
96
|
+
debug "Loading plugin..."
|
97
|
+
if job = Plugin.last_defined.load( last_run, (memory || Hash.new),
|
98
|
+
plugin[:options] || Hash.new )
|
99
|
+
info "Plugin loaded."
|
100
|
+
debug "Running plugin..."
|
101
|
+
begin
|
102
|
+
data = {}
|
103
|
+
Timeout.timeout(PLUGIN_TIMEOUT, PluginTimeoutError) do
|
104
|
+
data = job.run
|
105
|
+
end
|
106
|
+
rescue Timeout::Error
|
107
|
+
error "Plugin took too long to run."
|
108
|
+
return
|
109
|
+
rescue Exception
|
110
|
+
error "Plugin failed to run: #{$!.backtrace}"
|
111
|
+
end
|
112
|
+
info "Plugin completed its run."
|
113
|
+
|
114
|
+
# handle single report or array of reports
|
115
|
+
send_report(data[:report], plugin[:plugin_id]) if data[:report]
|
116
|
+
if data[:reports] and not data[:reports].empty?
|
117
|
+
data[:reports].each { |r| send_report(r, plugin[:plugin_id]) }
|
118
|
+
end
|
119
|
+
# handle single alert or array of alerts
|
120
|
+
send_alert(data[:alert], plugin[:plugin_id]) if data[:alert]
|
121
|
+
if data[:alerts] and not data[:alerts].empty?
|
122
|
+
data[:alerts].each { |a| send_alert(a, plugin[:plugin_id]) }
|
123
|
+
end
|
124
|
+
# handle single error or array of errors
|
125
|
+
send_error(data[:error], plugin[:plugin_id]) if data[:error]
|
126
|
+
if data[:errors] and not data[:errors].empty?
|
127
|
+
data[:errors].each { |e| send_error(e, plugin[:plugin_id]) }
|
128
|
+
end
|
129
|
+
|
130
|
+
@history["last_runs"][plugin[:name]] = run_time
|
131
|
+
@history["memory"][plugin[:name]] = data[:memory]
|
132
|
+
else
|
133
|
+
scout_error({:subject => "Plugin would not load."}, plugin[:plugin_id])
|
134
|
+
end
|
135
|
+
else
|
136
|
+
debug "Plugin does not need to be run at this time. " +
|
137
|
+
"(last run: #{last_run || 'nil'})"
|
138
|
+
end
|
139
|
+
info "Plugin #{plugin[:name]} processing complete."
|
140
|
+
data
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# Retrieves the Plugin Plan from the server. This is the list of plugins
|
145
|
+
# to execute, along with all options.
|
146
|
+
#
|
147
|
+
def plan
|
148
|
+
url = urlify(:plan)
|
149
|
+
info "Loading plan from #{url}..."
|
150
|
+
get(url, "Could not retrieve plan from server.") do |res|
|
151
|
+
begin
|
152
|
+
plugin_execution_plan = Marshal.load(res.body)
|
153
|
+
info "Plan loaded. (#{plugin_execution_plan.size} plugins: " +
|
154
|
+
"#{plugin_execution_plan.map { |p| p[:name] }.join(', ')})"
|
155
|
+
rescue TypeError
|
156
|
+
fatal "Plan from server was malformed."
|
157
|
+
exit
|
158
|
+
end
|
159
|
+
plugin_execution_plan.each do |plugin|
|
160
|
+
begin
|
161
|
+
yield plugin if block_given?
|
162
|
+
rescue RuntimeError
|
163
|
+
scout_error( { :subject => "Exception: #{$!.message}.",
|
164
|
+
:body => $!.backtrace },
|
165
|
+
plugin[:plugin_id] )
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
alias_method :test, :plan
|
171
|
+
|
172
|
+
# Sends report data to the Scout Server.
|
173
|
+
def send_report(data, plugin_id)
|
174
|
+
url = urlify(:report, :plugin_id => plugin_id)
|
175
|
+
report_hash = {:data => data, :plugin_id => plugin_id}
|
176
|
+
|
177
|
+
# add in any special fields
|
178
|
+
if time = ( data.delete(:scout_time) || data.delete("scout_time") )
|
179
|
+
report_hash[:time] = time
|
180
|
+
end
|
181
|
+
|
182
|
+
debug "Sending report to #{url} (#{data.inspect})..."
|
183
|
+
post url,
|
184
|
+
"Unable to send report to server.",
|
185
|
+
:report => report_hash
|
186
|
+
info "Report sent."
|
187
|
+
end
|
188
|
+
|
189
|
+
# Sends an alert to the Scout Server.
|
190
|
+
def send_alert(data, plugin_id)
|
191
|
+
url = urlify(:alert, :plugin_id => plugin_id)
|
192
|
+
debug "Sending alert to #{url} (subject: #{data[:subject]})..."
|
193
|
+
post url,
|
194
|
+
"Unable to send alert to server.",
|
195
|
+
:alert => data.merge(:plugin_id => plugin_id)
|
196
|
+
info "Alert sent."
|
197
|
+
end
|
198
|
+
|
199
|
+
# Sends an error to the Scout Server.
|
200
|
+
def send_error(data, plugin_id)
|
201
|
+
url = urlify(:error, :plugin_id => plugin_id)
|
202
|
+
debug "Sending error to #{url} (subject: #{data[:subject]})..."
|
203
|
+
post url,
|
204
|
+
"Unable to log error on server.",
|
205
|
+
:error => data.merge(:plugin_id => plugin_id)
|
206
|
+
info "Error sent."
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
def urlify(url_name, options = Hash.new)
|
212
|
+
return unless @server
|
213
|
+
options.merge!(:client_version => Scout::VERSION)
|
214
|
+
URI.join( @server,
|
215
|
+
URLS[url_name].
|
216
|
+
gsub(/\bCLIENT_KEY\b/, @client_key).
|
217
|
+
gsub(/\b[A-Z_]+\b/) { |k| options[k.downcase.to_sym] || k } )
|
218
|
+
end
|
219
|
+
|
220
|
+
def paramify(params, prefix = nil)
|
221
|
+
params.inject(Hash.new) do |all, (key, value)|
|
222
|
+
parent = prefix ? "#{prefix}[#{key}]" : String(key)
|
223
|
+
if value.is_a? Hash
|
224
|
+
all.merge(paramify(value, parent))
|
225
|
+
else
|
226
|
+
all.merge(parent => String(value))
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def post(url, error, params = {}, &response_handler)
|
232
|
+
return unless url
|
233
|
+
request(url, response_handler, error) do |connection|
|
234
|
+
post = Net::HTTP::Post.new(url.path + (url.query ? ('?' + url.query) : ''))
|
235
|
+
post.set_form_data(paramify(params))
|
236
|
+
connection.request(post)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def get(url, error, params = {}, &response_handler)
|
241
|
+
return unless url
|
242
|
+
request(url, response_handler, error) do |connection|
|
243
|
+
connection.get(url.path + (url.query ? ('?' + url.query) : ''))
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def request(url, response_handler, error, &connector)
|
248
|
+
http = Net::HTTP.new(url.host, url.port)
|
249
|
+
if url.is_a? URI::HTTPS
|
250
|
+
http.use_ssl = true
|
251
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
252
|
+
end
|
253
|
+
case response = no_warnings { http.start(&connector) }
|
254
|
+
when Net::HTTPSuccess
|
255
|
+
response_handler[response] unless response_handler.nil?
|
256
|
+
else
|
257
|
+
fatal error
|
258
|
+
exit
|
259
|
+
end
|
260
|
+
rescue Timeout::Error
|
261
|
+
fatal "Request timed out."
|
262
|
+
exit
|
263
|
+
rescue Exception
|
264
|
+
fatal "An HTTP error occurred: #{$!.message}"
|
265
|
+
exit
|
266
|
+
end
|
267
|
+
|
268
|
+
def no_warnings
|
269
|
+
old_verbose = $VERBOSE
|
270
|
+
$VERBOSE = false
|
271
|
+
yield
|
272
|
+
ensure
|
273
|
+
$VERBOSE = old_verbose
|
274
|
+
end
|
275
|
+
|
276
|
+
# Forward Logger methods to an active instance, when there is one.
|
277
|
+
def method_missing(meth, *args, &block)
|
278
|
+
if (Logger::SEV_LABEL - %w[ANY]).include? meth.to_s.upcase
|
279
|
+
@logger.send(meth, *args, &block) unless @logger.nil?
|
280
|
+
else
|
281
|
+
super
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|