scout 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|