scout 5.1.5 → 5.2.1

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/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ == 5.2.1
2
+
3
+ * Added private-key based code signing
4
+ * Added local plugin overrides
5
+ * Added local ad-hoc plugins
6
+
1
7
  == 5.1.5
2
8
 
3
9
  * Added sleep interval directive. Agent will only sleep when used in non-interactive mode.
@@ -0,0 +1,9 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEA7y0b7SAkaqkKvXSQiN6o
3
+ u1/T56dpx8h74p61Gn4i1dwtogqsa/qSf+K7waFMu7XUeMx3z5ZabLu5ZZLONUFA
4
+ SbV4dN3dtz1ECSMuOleXsM+0HzyV8FqLZ1h6XZ65gUhbmTKRUcIfgse0lLWoVKxX
5
+ gtcaLdv/5nrZwZH9g5asGuzqyVx4JwopR/aedFsXSBZyo+FyLQoINrNL2lVllefR
6
+ 3EsVDLyZ0etL+K1MtVu1geS41dw4KBCAZQL6eO/88+A9wlJxCcWjXiBK6Gz55ytP
7
+ 1w1QrJ25MGVTrE89KGX0wQ9RJ5SLhuFcvRDhNO2T+4H4VZdEjIsIH68YUDNe5FnR
8
+ cwIBIw==
9
+ -----END PUBLIC KEY-----
data/lib/scout.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env ruby -wKU
2
2
 
3
3
  module Scout
4
- VERSION = "5.1.5".freeze
4
+ VERSION = "5.2.1".freeze
5
5
  end
6
6
 
7
7
  require "scout/command"
8
8
  require "scout/plugin"
9
9
  require "scout/plugin_options"
10
+ require "scout/scout_logger"
10
11
  require "scout/server"
data/lib/scout/command.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env ruby -wKU
2
2
 
3
3
  require "optparse"
4
- require "logger"
5
4
  require "fileutils"
6
5
 
7
6
  module Scout
@@ -37,6 +36,9 @@ module Scout
37
36
  opts.separator " #{program_name}"
38
37
  opts.separator " ... OR ..."
39
38
  opts.separator " #{program_name} [OPTIONS] install"
39
+ opts.separator " Troubleshoot:"
40
+ opts.separator " #{program_name} troubleshoot"
41
+ opts.separator " ... print Scout environment info. Recommend directing output to a file."
40
42
  opts.separator " Local plugin testing:"
41
43
  opts.separator " #{program_name} [OPTIONS] test " +
42
44
  "PATH_TO_PLUGIN [PLUGIN_OPTIONS]"
@@ -136,17 +138,18 @@ module Scout
136
138
  @force = options[:force] || false
137
139
 
138
140
  @args = args
139
- end
140
-
141
- attr_reader :server, :history
142
141
 
143
- def config_dir
144
- return @config_dir if defined? @config_dir
142
+ # create config dir if necessary
145
143
  @config_dir = File.dirname(history)
146
144
  FileUtils.mkdir_p(@config_dir) # ensure dir exists
147
- @config_dir
145
+
146
+ @log_path = File.join(@config_dir, "latest_run.log")
147
+
148
148
  end
149
149
 
150
+ attr_reader :server, :history, :config_dir, :log_path
151
+
152
+
150
153
  def verbose?
151
154
  @verbose
152
155
  end
@@ -154,12 +157,15 @@ module Scout
154
157
  def log
155
158
  return @log if defined? @log
156
159
  @log = if verbose?
157
- log = Logger.new($stdout)
160
+ log = ScoutLogger.new($stdout)
158
161
  log.datetime_format = "%Y-%m-%d %H:%M:%S "
159
162
  log.level = level
160
163
  log
161
164
  else
162
- nil
165
+ log = ScoutLogger.new(nil)
166
+ log.datetime_format = "%Y-%m-%d %H:%M:%S "
167
+ log.level = Logger::DEBUG
168
+ log
163
169
  end
164
170
  end
165
171
 
@@ -30,8 +30,25 @@ module Scout
30
30
  create_pid_file_or_exit
31
31
  @scout.run_plugins_by_plan
32
32
  @scout.save_history
33
+
34
+ begin
35
+ # Since this is a new checkin, overwrite the existing log
36
+ File.open(log_path, "w") do|log_file|
37
+ log_file.puts log.messages # log.messages is an array of every message logged during this run
38
+ end
39
+ rescue
40
+ log.info "Could not write to #{log_path}."
41
+ end
33
42
  else
34
43
  log.info "Not time to checkin yet. Next checkin in #{@scout.next_checkin}. Override by passing --force to the scout command" if log
44
+ begin
45
+ # Since this a ping, append to the existing log
46
+ File.open(log_path, "a") do|log_file|
47
+ log_file.puts log.messages
48
+ end
49
+ rescue
50
+ log.info "Could not write to #{log_path}."
51
+ end
35
52
  end
36
53
  end
37
54
  end
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ require "pp"
4
+
5
+ module Scout
6
+ class Command
7
+ class Troubleshoot < Command
8
+ def initialize(options, args)
9
+ @contents=[]
10
+ super
11
+ end
12
+
13
+ def run
14
+ puts "Gathering troubleshooting information about your Scout install ... "
15
+
16
+ heading "Scout Info"
17
+ bullet "History file", history
18
+ bullet "Version", Scout::VERSION
19
+
20
+ heading "Latest Log"
21
+ contents=File.read(log_path) rescue "Log not found at #{log_path}"
22
+ text contents
23
+
24
+ heading "Rubygems Environment"
25
+ text `gem env`
26
+
27
+ heading "Ruby info"
28
+ bullet "Path to executable", `which ruby`
29
+ bullet "Version", `ruby -v`
30
+ bullet "Ruby's internal path", $:.join(', ')
31
+
32
+ heading "Installed Gems"
33
+ text `gem list --local`
34
+
35
+ heading "History file Contents"
36
+ contents=File.read(history) rescue "History not found at #{log_path}"
37
+ text contents
38
+
39
+ heading "Agent directory Contents"
40
+ text `ls -la #{config_dir}`
41
+
42
+ heading ""
43
+
44
+ puts "Done"
45
+
46
+ puts @contents.join("\n")
47
+
48
+ end
49
+ end
50
+
51
+ private
52
+ def heading(s)
53
+ @contents += ["",s,"**************************************************************************************************",""]
54
+ end
55
+
56
+ def bullet(label,s)
57
+ @contents << " - #{label} : #{s.chomp}"
58
+ end
59
+
60
+ def text(s)
61
+ @contents << s
62
+ end
63
+
64
+ end
65
+ end
data/lib/scout/plugin.rb CHANGED
@@ -59,8 +59,7 @@ module Scout
59
59
  end
60
60
 
61
61
  def option(name)
62
- @options[name] ||
63
- @options[name.is_a?(String) ? name.to_sym : String(name)]
62
+ @options[name] || @options[name.is_a?(String) ? name.to_sym : String(name)]
64
63
  end
65
64
 
66
65
  # Builds the data to send to the server.
@@ -0,0 +1,19 @@
1
+ # We use this subclass of Logger in the Scout Agent so we can retrieve all the logged messages at the end of the run.
2
+ # This works well only because the Agent is not a long-running process.
3
+
4
+ require 'logger'
5
+
6
+ class ScoutLogger < Logger
7
+ attr_reader :messages
8
+
9
+ def initialize(*args)
10
+ @messages=[]
11
+ super
12
+ end
13
+
14
+ # this is the method that ultimately gets called whenever you invoke info, debug, etc.
15
+ def add(severity, message=nil, progname = nil, &block)
16
+ @messages << "[#{Time.now.strftime('%Y-%m-%d %H:%M:%S ')} ##{$$}] : #{progname}"
17
+ super
18
+ end
19
+ end
data/lib/scout/server.rb CHANGED
@@ -7,6 +7,7 @@ require "timeout"
7
7
  require "stringio"
8
8
  require "zlib"
9
9
  require "socket"
10
+ require "base64"
10
11
 
11
12
  $LOAD_PATH << File.join(File.dirname(__FILE__), *%w[.. .. vendor json_pure lib])
12
13
  require "json"
@@ -37,6 +38,7 @@ module Scout
37
38
 
38
39
  attr_reader :new_plan
39
40
  attr_reader :directives
41
+ attr_reader :plugin_config
40
42
 
41
43
  # Creates a new Scout Server connection.
42
44
  def initialize(server, client_key, history_file, logger = nil)
@@ -48,6 +50,9 @@ module Scout
48
50
  @plugin_plan = []
49
51
  @directives = {} # take_snapshots, interval, sleep_interval
50
52
  @new_plan = false
53
+ @local_plugin_path = File.dirname(history_file) # just put overrides and ad-hoc plugins in same directory as history file.
54
+ @plugin_config_path = File.join(@local_plugin_path, "plugins.properties")
55
+ @plugin_config = load_plugin_configs(@plugin_config_path)
51
56
 
52
57
  # the block is only passed for install and test, since we split plan retrieval outside the lockfile for run
53
58
  if block_given?
@@ -62,12 +67,13 @@ module Scout
62
67
  # to execute, along with all options.
63
68
  #
64
69
  # This method has a couple of side effects:
65
- # 1) it sets the @plugin plan with either A) whatever is in history, B) the results of the /plan retrieval
70
+ # 1) it sets the @plugin_plan with either A) whatever is in history, B) the results of the /plan retrieval
66
71
  # 2) it sets @checkin_to = true IF so directed by the scout server
67
72
  def fetch_plan
68
73
  url = urlify(:plan)
69
74
  info "Pinging server at #{url}..."
70
75
  headers = {"x-scout-tty" => ($stdin.tty? ? 'true' : 'false')}
76
+
71
77
  if @history["plan_last_modified"] and @history["old_plugins"]
72
78
  headers["If-Modified-Since"] = @history["plan_last_modified"]
73
79
  end
@@ -75,7 +81,10 @@ module Scout
75
81
  if res.is_a? Net::HTTPNotModified
76
82
  info "Plan not modified. Will reuse saved plan."
77
83
  @plugin_plan = Array(@history["old_plugins"])
84
+ # Add local plugins to the plan. Note that local plugins are NOT saved to history file
85
+ @plugin_plan += get_local_plugins
78
86
  @directives = @history["directives"] || Hash.new
87
+
79
88
  else
80
89
  info "plan has been modified. Will run the new plan now."
81
90
  begin
@@ -85,26 +94,80 @@ module Scout
85
94
  end
86
95
 
87
96
  body_as_hash = JSON.parse(body)
88
- @plugin_plan = Array(body_as_hash["plugins"])
89
- @directives = body_as_hash["directives"].is_a?(Hash) ? body_as_hash["directives"] : Hash.new
90
97
 
91
- @history["plan_last_modified"] = res["last-modified"]
92
- @history["old_plugins"] = @plugin_plan
93
- @history["directives"] = @directives
98
+ # Ensure all the plugins in the new plan are properly signed. Load the public key for this.
99
+ public_key_text = File.read(File.join( File.dirname(__FILE__), *%w[.. .. data code_id_rsa.pub] ))
100
+ debug "Loaded public key used for verifying code signatures (#{public_key_text.size} bytes)"
101
+ code_public_key = OpenSSL::PKey::RSA.new(public_key_text)
94
102
 
95
- info "Plan loaded. (#{@plugin_plan.size} plugins: " +
96
- "#{@plugin_plan.map { |p| p['name'] }.join(', ')})" +
97
- ". Directives: #{@directives.to_a.map{|a| "#{a.first}:#{a.last}"}.join(", ")}"
103
+ temp_plugins=Array(body_as_hash["plugins"])
104
+ plugin_signature_error = false
105
+ temp_plugins.each do |plugin|
106
+ signature=plugin['signature']
107
+ id_and_name = "#{plugin['id']}-#{plugin['name']}".sub(/\A-/, "")
108
+ if signature
109
+ code=plugin['code'].gsub(/ +$/,'') # we strip trailing whitespace before calculating signatures. Same here.
110
+ decoded_signature=Base64.decode64(signature)
111
+ if !code_public_key.verify(OpenSSL::Digest::SHA1.new, decoded_signature, code)
112
+ info "#{id_and_name} signature doesn't match!"
113
+ plugin_signature_error=true
114
+ end
115
+ else
116
+ info "#{id_and_name} has no signature!"
117
+ plugin_signature_error=true
118
+ end
119
+ end
98
120
 
99
- @new_plan = true # used in determination if we should checkin this time or not
100
- rescue Exception
101
- fatal "Plan from server was malformed."
121
+
122
+ if(!plugin_signature_error)
123
+ @plugin_plan = temp_plugins
124
+ @directives = body_as_hash["directives"].is_a?(Hash) ? body_as_hash["directives"] : Hash.new
125
+ @history["plan_last_modified"] = res["last-modified"]
126
+ @history["old_plugins"] = @plugin_plan.clone # important that the plan is cloned -- we're going to add local plugins, and they shouldn't go into history
127
+ @history["directives"] = @directives
128
+
129
+ info "Plan loaded. (#{@plugin_plan.size} plugins: " +
130
+ "#{@plugin_plan.map { |p| p['name'] }.join(', ')})" +
131
+ ". Directives: #{@directives.to_a.map{|a| "#{a.first}:#{a.last}"}.join(", ")}"
132
+
133
+ @new_plan = true # used in determination if we should checkin this time or not
134
+ else
135
+ info "There was a problem with plugin signatures. Reusing old plan."
136
+ @plugin_plan = Array(@history["old_plugins"])
137
+ @directives = @history["directives"] || Hash.new
138
+ end
139
+
140
+ # Add local plugins to the plan. Note that local plugins are NOT saved to history file
141
+ @plugin_plan += get_local_plugins
142
+ rescue Exception =>e
143
+ fatal "Plan from server was malformed: #{e.message} - #{e.backtrace}"
102
144
  exit
103
145
  end
104
146
  end
105
147
  end
106
148
  end
107
-
149
+
150
+ # returns an array of hashes representing local plugins found on the filesystem
151
+ # The glob pattern requires that filenames begin with a letter,
152
+ # which excludes plugin overrides (like 12345.rb)
153
+ def get_local_plugins
154
+ local_plugin_paths=Dir.glob(File.join(@local_plugin_path,"[a-zA-Z]*.rb"))
155
+ local_plugin_paths.map do |plugin_path|
156
+ begin
157
+ {
158
+ 'name' => File.basename(plugin_path),
159
+ 'local_filename' => File.basename(plugin_path),
160
+ 'origin' => 'LOCAL',
161
+ 'code' => File.read(plugin_path),
162
+ 'interval' => 0
163
+ }
164
+ rescue => e
165
+ info "Error trying to read local plugin: #{plugin_path} -- #{e.backtrace.join('\n')}"
166
+ nil
167
+ end
168
+ end.compact
169
+ end
170
+
108
171
  # To distribute pings across a longer timeframe, the agent will sleep for a given
109
172
  # amount of time. When using the --force option the sleep_interval is ignored.
110
173
  def sleep_interval
@@ -153,11 +216,12 @@ module Scout
153
216
  process_plugin(plugin)
154
217
  rescue Exception
155
218
  @checkin[:errors] << build_report(
156
- plugin['id'],
219
+ plugin,
157
220
  :subject => "Exception: #{$!.message}.",
158
221
  :body => $!.backtrace
159
222
  )
160
223
  error("Encountered an error: #{$!.message}")
224
+ puts $!.backtrace.join('\n')
161
225
  end
162
226
  end
163
227
  take_snapshot if @directives['take_snapshots']
@@ -172,10 +236,12 @@ module Scout
172
236
  # It then loads the plugin and runs it with a PLUGIN_TIMEOUT time limit.
173
237
  # The plugin generates data, alerts, and errors. In addition, it will
174
238
  # set memory and last_run information in the history file.
175
- #
239
+ #
240
+ # The plugin argument is a hash with keys: id, name, code, timeout, options, signature.
176
241
  def process_plugin(plugin)
177
242
  info "Processing the '#{plugin['name']}' plugin:"
178
243
  id_and_name = "#{plugin['id']}-#{plugin['name']}".sub(/\A-/, "")
244
+ plugin_id = plugin['id']
179
245
  last_run = @history["last_runs"][id_and_name] ||
180
246
  @history["last_runs"][plugin['name']]
181
247
  memory = @history["memory"][id_and_name] ||
@@ -186,21 +252,47 @@ module Scout
186
252
  if last_run.nil? or delta.between?(-RUN_DELTA, 0) or delta >= 0
187
253
  debug "Plugin is past interval and needs to be run. " +
188
254
  "(last run: #{last_run || 'nil'})"
255
+ code_to_run = plugin['code']
256
+ if plugin_id && plugin_id != ""
257
+ override_path=File.join(@local_plugin_path, "#{plugin_id}.rb")
258
+ # debug "Checking for local plugin override file at #{override_path}"
259
+ if File.exist?(override_path)
260
+ code_to_run = File.read(override_path)
261
+ debug "Override file found - Using #{code_to_run.size} chars of code in #{override_path} for plugin id=#{plugin_id}"
262
+ plugin['origin'] = "OVERRIDE"
263
+ else
264
+ plugin['origin'] = nil
265
+ end
266
+ end
189
267
  debug "Compiling plugin..."
190
268
  begin
191
- eval( plugin['code'],
269
+ eval( code_to_run,
192
270
  TOPLEVEL_BINDING,
193
271
  plugin['path'] || plugin['name'] )
194
272
  info "Plugin compiled."
195
273
  rescue Exception
196
274
  raise if $!.is_a? SystemExit
197
275
  error "Plugin would not compile: #{$!.message}"
198
- @checkin[:errors] << build_report(plugin['id'],:subject => "Plugin would not compile", :body=>"#{$!.message}\n\n#{$!.backtrace}")
276
+ @checkin[:errors] << build_report(plugin,:subject => "Plugin would not compile", :body=>"#{$!.message}\n\n#{$!.backtrace}")
199
277
  return
200
278
  end
279
+
280
+ # Lookup any local options in plugin_config.properies as needed
281
+ options=(plugin['options'] || Hash.new)
282
+ options.each_pair do |k,v|
283
+ if v=~/^lookup:(.+)$/
284
+ lookup_key = $1.strip
285
+ if plugin_config[lookup_key]
286
+ options[k]=plugin_config[lookup_key]
287
+ else
288
+ info "Plugin #{id_and_name}: option #{k} appears to be a lookup, but we can't find #{lookup_key} in #{@plugin_config_path}"
289
+ end
290
+ end
291
+ end
292
+
293
+
201
294
  debug "Loading plugin..."
202
- if job = Plugin.last_defined.load( last_run, (memory || Hash.new),
203
- plugin['options'] || Hash.new )
295
+ if job = Plugin.last_defined.load( last_run, (memory || Hash.new), options)
204
296
  info "Plugin loaded."
205
297
  debug "Running plugin..."
206
298
  begin
@@ -212,7 +304,7 @@ module Scout
212
304
  end
213
305
  rescue Timeout::Error, PluginTimeoutError
214
306
  error "Plugin took too long to run."
215
- @checkin[:errors] << build_report(plugin['id'],
307
+ @checkin[:errors] << build_report(plugin,
216
308
  :subject => "Plugin took too long to run",
217
309
  :body=>"Execution timed out.")
218
310
  return
@@ -220,7 +312,7 @@ module Scout
220
312
  raise if $!.is_a? SystemExit
221
313
  error "Plugin failed to run: #{$!.class}: #{$!.message}\n" +
222
314
  "#{$!.backtrace.join("\n")}"
223
- @checkin[:errors] << build_report(plugin['id'],
315
+ @checkin[:errors] << build_report(plugin,
224
316
  :subject => "Plugin failed to run",
225
317
  :body=>"#{$!.class}: #{$!.message}\n#{$!.backtrace.join("\n")}")
226
318
  end
@@ -234,7 +326,7 @@ module Scout
234
326
  reports << report
235
327
  end
236
328
  reports.each do |fields|
237
- @checkin[plural] << build_report(plugin['id'], fields)
329
+ @checkin[plural] << build_report(plugin, fields)
238
330
  end
239
331
  end
240
332
 
@@ -244,7 +336,7 @@ module Scout
244
336
  @history["memory"][id_and_name] = data[:memory]
245
337
  else
246
338
  @checkin[:errors] << build_report(
247
- plugin['id'],
339
+ plugin,
248
340
  :subject => "Plugin would not load."
249
341
  )
250
342
  end
@@ -285,7 +377,8 @@ module Scout
285
377
  :alerts => Array.new,
286
378
  :errors => Array.new,
287
379
  :summaries => Array.new,
288
- :snapshot => '' }
380
+ :snapshot => '',
381
+ :config_path => File.expand_path(File.dirname(@history_file))}
289
382
  end
290
383
 
291
384
  def show_checkin(printer = :p)
@@ -323,10 +416,13 @@ module Scout
323
416
 
324
417
  private
325
418
 
326
- def build_report(plugin_id, fields)
327
- { :plugin_id => plugin_id,
419
+ def build_report(plugin_hash, fields)
420
+ { :plugin_id => plugin_hash['id'],
328
421
  :created_at => Time.now.utc.strftime("%Y-%m-%d %H:%M:%S"),
329
- :fields => fields }
422
+ :fields => fields,
423
+ :local_filename => plugin_hash['local_filename'], # this will be nil unless it's an ad-hoc plugin
424
+ :origin => plugin_hash['origin'] # [LOCAL|OVERRIDE|nil]
425
+ }
330
426
  end
331
427
 
332
428
  def urlify(url_name, options = Hash.new)
@@ -422,5 +518,31 @@ module Scout
422
518
  super
423
519
  end
424
520
  end
521
+
522
+ private
523
+
524
+ # Called during initialization; loads the plugin_configs (local plugin configurations for passwords, etc)
525
+ # if the file is there. Returns a hash like {"db.username"=>"secr3t"}
526
+ def load_plugin_configs(path)
527
+ temp_configs={}
528
+ if File.exist?(path)
529
+ debug "Loading Plugin Configs at #{path}"
530
+ begin
531
+ File.open(path,"r").read.each_line do |line|
532
+ line.strip!
533
+ next if line[0] == '#'
534
+ next unless line.include? "="
535
+ k,v =line.split('=')
536
+ temp_configs[k]=v
537
+ end
538
+ debug("#{temp_configs.size} plugin config(s) loaded.")
539
+ rescue
540
+ info "Error loading Plugin Configs at #{path}: #{$!}"
541
+ end
542
+ else
543
+ debug "No Plugin Configs at #{path}"
544
+ end
545
+ return temp_configs
546
+ end
425
547
  end
426
548
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout
3
3
  version: !ruby/object:Gem::Version
4
- hash: 57
5
- prerelease:
4
+ hash: 61
5
+ prerelease: false
6
6
  segments:
7
7
  - 5
8
+ - 2
8
9
  - 1
9
- - 5
10
- version: 5.1.5
10
+ version: 5.2.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Scout Monitoring
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-02-01 00:00:00 -08:00
18
+ date: 2011-02-28 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -52,12 +52,15 @@ files:
52
52
  - lib/scout/command/install.rb
53
53
  - lib/scout/command/run.rb
54
54
  - lib/scout/command/test.rb
55
+ - lib/scout/command/troubleshoot.rb
55
56
  - lib/scout/command.rb
56
57
  - lib/scout/plugin.rb
57
58
  - lib/scout/plugin_options.rb
59
+ - lib/scout/scout_logger.rb
58
60
  - lib/scout/server.rb
59
61
  - lib/scout.rb
60
62
  - data/cacert.pem
63
+ - data/code_id_rsa.pub
61
64
  - data/gpl-2.0.txt
62
65
  - data/lgpl-2.1.txt
63
66
  - vendor/json_pure/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkComparison.log
@@ -218,7 +221,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
218
221
  requirements: []
219
222
 
220
223
  rubyforge_project: scout
221
- rubygems_version: 1.4.2
224
+ rubygems_version: 1.3.7
222
225
  signing_key:
223
226
  specification_version: 3
224
227
  summary: Scout makes monitoring and reporting on your web applications as flexible and simple as possible.