scout_agent 3.0.7 → 3.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.
@@ -2,20 +2,35 @@
2
2
  # encoding: UTF-8
3
3
 
4
4
  module ScoutAgent
5
+ #
6
+ # These objects are the data gathers periodically run by the agent. To gather
7
+ # a new type of data, you inherit from this Class and provide a build_report()
8
+ # method that calls some combination of report(), alert(), and error() to send
9
+ # data to the Scout server.
10
+ #
5
11
  class Mission
6
- class << self
7
- attr_reader :prepared
12
+ #
13
+ # This method returns the last Mission subclass that was loaded. The
14
+ # Mission runner uses this after compiling Mission code to get the newly
15
+ # loaded object.
16
+ #
17
+ def self.prepared
18
+ @prepared ||= nil
8
19
  end
9
-
10
- def self.inherited(new_plugin)
11
- @prepared = new_plugin
20
+
21
+ #
22
+ # Hooks into Ruby's Class loading process so we can keep track of the last
23
+ # loaded subclass.
24
+ #
25
+ def self.inherited(new_mission)
26
+ @prepared = new_mission
12
27
  end
13
28
 
14
29
  #
15
- # You can use this method to indicate one or more libraries your plugin
30
+ # You can use this method to indicate one or more libraries your Mission
16
31
  # requires:
17
32
  #
18
- # class MyNeedyPlugin < Scout::Plugin
33
+ # class MyNeedyPlugin < ScoutAgent::Mission
19
34
  # needs "faster_csv", "elif"
20
35
  # # ...
21
36
  # end
@@ -25,13 +40,18 @@ module ScoutAgent
25
40
  #
26
41
  def self.needs(*libraries)
27
42
  if libraries.empty?
28
- @needs ||= [ ]
43
+ @needs ||= Array.new
29
44
  else
30
45
  needs.push(*libraries.flatten)
31
46
  end
32
47
  end
33
48
 
34
- # Creates a new Scout Plugin to run.
49
+ #
50
+ # Creates a new Scout Mission to run. It requires the +id+ and +name+ for
51
+ # the Mission as well as the +last_run+ Time, +memory+, and +options+ from
52
+ # the Mission database. You may optionally provide a +log+ for the Mission
53
+ # to write messages to.
54
+ #
35
55
  def initialize(id, name, last_run, memory, options, log = WireTap.new(nil))
36
56
  @id = id
37
57
  @name = name
@@ -43,77 +63,145 @@ module ScoutAgent
43
63
  @queue_db = !testing? && Database.load(:queue, @log)
44
64
  end
45
65
 
66
+ #
67
+ # The numerical ID this Mission is known by to the Scout server. This is
68
+ # <tt>:testing</tt> for a Mission running in the agent's test mode.
69
+ #
70
+ attr_reader :id
71
+ # The human readable name of this Mission.
72
+ attr_reader :name
73
+ # The last run Time for this Mission. Can be +nil+ for new Missions.
46
74
  attr_reader :last_run
75
+ # The log file this Mission is allowed to append messages to.
47
76
  attr_reader :log
77
+ # A common alias used in Rails code.
48
78
  alias_method :logger, :log
49
79
 
80
+ #
81
+ # Returns +true+ if this Mission is currently being run in the agent's test
82
+ # mode.
83
+ #
50
84
  def testing?
51
85
  @id == :testing
52
86
  end
53
87
 
88
+ #
89
+ # Fetches an option passed to this Mission from the Web interface by +name+.
90
+ # If a direct lookup fails, it will be attempted again after converting a
91
+ # String +name+ to a Symbol or any other +name+ to a String. This makes the
92
+ # lookups feel familiar to HashWithIndifferentAccess fans.
93
+ #
54
94
  def option(name)
55
95
  @options[name] ||
56
96
  @options[name.is_a?(String) ? name.to_sym : String(name)]
57
97
  end
58
-
59
- # Builds the data to send to the server.
98
+
60
99
  #
61
- # We programatically define several helper methods for creating this data.
100
+ # This method returns an Array of the reports that will be saved to the
101
+ # database at the end of this execution. It can be used to edit reports
102
+ # you have already entered.
62
103
  #
63
- # Usage:
104
+ def reports
105
+ data_for_server[:reports]
106
+ end
107
+
108
+ #
109
+ # This method adds a +new_report+ that will be saved to the database at the
110
+ # end of the current Mission run. The passed +new_report+ should be a Hash
111
+ # of the data values you wish to track.
64
112
  #
65
- # reports << {:data => "here"}
66
- # report(:data => "here")
67
- # add_report(:data => "here")
113
+ def report(new_report)
114
+ reports << new_report
115
+ end
116
+ # Another way to add reports.
117
+ alias_method :add_report, :report
118
+
119
+ #
120
+ # This method returns an Array of the hints that will be saved to the
121
+ # database at the end of this execution. It can be used to edit hints you
122
+ # have already entered.
68
123
  #
69
- # alerts << {:subject => "subject", :body => "body"}
70
- # alert("subject", "body")
71
- # alert(:subject => "subject", :body => "body")
72
- # add_alert("subject", "body")
73
- # add_alert(:subject => "subject", :body => "body")
124
+ def hints
125
+ data_for_server[:hints]
126
+ end
127
+
128
+ #
129
+ # This method adds a +new_hint+ that will be saved to the database at the
130
+ # end of the current Mission run. The passed +new_hint+ should be a Hash of
131
+ # the data values you wish to track.
74
132
  #
75
- # errors << {:subject => "subject", :body => "body"}
76
- # error("subject", "body")
77
- # error(:subject => "subject", :body => "body")
78
- # add_error("subject", "body")
79
- # add_error(:subject => "subject", :body => "body")
80
- #
81
- def data_for_server
82
- @data_for_server ||= { :reports => [ ],
83
- :hints => [ ],
84
- :alerts => [ ],
85
- :errors => [ ],
86
- :memory => { } }
133
+ def hint(new_hint)
134
+ hints << new_hint
87
135
  end
136
+ # Another way to add hints.
137
+ alias_method :add_hint, :hint
88
138
 
89
- %w[report hint alert error].each do |kind|
90
- class_eval <<-END
91
- def #{kind}s
92
- data_for_server[:#{kind}s]
93
- end
94
-
95
- if %w[report hint].include? "#{kind}"
96
- def #{kind}(new_entry)
97
- #{kind}s << new_entry
98
- end
99
- else
100
- def #{kind}(*fields)
101
- #{kind}s << ( fields.first.is_a?(Hash) ?
102
- fields.first :
103
- {:subject => fields.first, :body => fields.last} )
104
- end
105
- end
106
- alias_method :add_#{kind}, :#{kind}
107
- END
139
+ #
140
+ # This method returns an Array of the alerts that will be saved to the
141
+ # database at the end of this execution. It can be used to edit alerts you
142
+ # have already entered.
143
+ #
144
+ def alerts
145
+ data_for_server[:alerts]
108
146
  end
109
147
 
110
148
  #
111
- # Usage:
149
+ # :call-seq:
150
+ # alert(subject, body)
151
+ # alert(new_alert)
112
152
  #
113
- # memory(:no_track)
114
- # memory.delete(:no_track)
153
+ # This method adds an alert that will be saved to the database at the end of
154
+ # the current Mission run. You can pass the +subject+ and +body+ of the
155
+ # generated alert separately, or a Hash containing <tt>:subject</tt> and
156
+ # <tt>:body</tt> values for the +new_alert+.
157
+ #
158
+ def alert(*new_alert)
159
+ alerts << build_alert_or_error(new_alert)
160
+ end
161
+ # Another way to add alerts.
162
+ alias_method :add_alert, :alert
163
+
164
+ #
165
+ # This method returns an Array of the errors that will be saved to the
166
+ # database at the end of this execution. It can be used to edit errors you
167
+ # have already entered.
168
+ #
169
+ def errors
170
+ data_for_server[:errors]
171
+ end
172
+
173
+ #
174
+ # :call-seq:
175
+ # error(subject, body)
176
+ # error(new_error)
177
+ #
178
+ # This method adds an error that will be saved to the database at the end of
179
+ # the current Mission run. You can pass the +subject+ and +body+ of the
180
+ # generated error separately, or a Hash containing <tt>:subject</tt> and
181
+ # <tt>:body</tt> values for the +new_error+.
182
+ #
183
+ def error(*new_error)
184
+ errors << build_alert_or_error(new_error)
185
+ end
186
+ # Another way to add errors.
187
+ alias_method :add_error, :error
188
+
189
+ #
190
+ # :call-seq:
191
+ # memory(name)
192
+ # memory.delete(name)
115
193
  # memory.clear
116
194
  #
195
+ # The primary usage of this method is to fetch values saved into the Mission
196
+ # memory during a previous execution by +name+. The +name+ lookup rules are
197
+ # The same as those decribed in the option() method, so you can use String
198
+ # and Symbol keys interchangeably.
199
+ #
200
+ # When called without a +name+, this method returns a Hash of <b>the
201
+ # current</b> Mission memory (not the remembered values from the previous
202
+ # execution). This is mainly helpful if you want to edit the values you
203
+ # have added to be saved during this run.
204
+ #
117
205
  def memory(name = nil)
118
206
  if name.nil?
119
207
  data_for_server[:memory]
@@ -124,20 +212,46 @@ module ScoutAgent
124
212
  end
125
213
 
126
214
  #
127
- # Usage:
215
+ # :call-seq:
216
+ # remember(name, value)
217
+ # remember(name1, value1, name2, value2, ...)
218
+ # remember(name => value)
219
+ # remember(name1 => value1, name2 => value2, ...)
220
+ # remember(name1, value1, ..., name2 => value2, ...)
128
221
  #
129
- # remember(:name, value)
130
- # remember(:name1, value1, :name2, value2)
131
- # remember(:name => value)
132
- # remember(:name1 => value1, :name2 => value2)
133
- # remember(:name1, value1, :name2 => value2)
222
+ # Adds all +name+ and +value+ pairs into the Mission memory that will be
223
+ # saved from the current execution.
224
+ #
225
+ # It's important to note that Mission memory is not automatically copied
226
+ # from the previous executions, so you must call this method during each
227
+ # execution if you need to keep saving a value.
134
228
  #
135
229
  def remember(*args)
230
+ # divide Hash and non-Hash arguments
136
231
  hashes, other = args.partition { |value| value.is_a? Hash }
137
- hashes.each { |hash| memory.merge!(hash) }
138
- (0...other.size).step(2) { |i| memory.merge!(other[i] => other[i + 1]) }
232
+ # merge all Hash arguments into the Mission memory
233
+ hashes.each do |hash|
234
+ memory.merge!(hash)
235
+ end
236
+ # merge all non-Hash arguments into the Mission memory
237
+ (0...other.size).step(2) do |i|
238
+ memory.merge!(other[i] => other[i + 1])
239
+ end
139
240
  end
140
241
 
242
+ #
243
+ # This method iterates over messages that have been queued from an external
244
+ # source for your Mission. Each message (the restored Ruby representation
245
+ # of the JSON data passed to the queue command) will be passed into your
246
+ # +block+ one by one. If your block accepts more than one argument, you
247
+ # will also be passed queue Times with the messages. The act of reading
248
+ # these messages removes them from the database so the current Mission
249
+ # should be used to pass them to the server as meaningful data or save them
250
+ # to Mission memory for later use.
251
+ #
252
+ # When a Mission is running in test mode, queued messages are read from
253
+ # <tt>options(:\_\_queue\_\_)</tt> and queue Times are simply <tt>Time.now</tt>.
254
+ #
141
255
  def each_queued_message(&block)
142
256
  if testing?
143
257
  Array(option(:__queue__)).each do |message|
@@ -160,6 +274,10 @@ module ScoutAgent
160
274
  end
161
275
 
162
276
  #
277
+ # This method is the wrapper over Scout's older interface for Mission (then
278
+ # called a Plugin) execution. It hands off to the newer build_report(),
279
+ # then records the results of that run.
280
+ #
163
281
  # Old plugins will work because they override this method. New plugins can
164
282
  # now leave this method in place, add a build_report() method instead, and
165
283
  # use the new helper methods to build up content inside which will
@@ -192,13 +310,15 @@ module ScoutAgent
192
310
  write_reports_and_update_memory
193
311
  end
194
312
 
313
+ #######
195
314
  private
315
+ #######
196
316
 
197
317
  #
198
- # Returns true is a library can be loaded. A bare require is made as the
199
- # first attempt. If that fails, RubyGems is loaded and another attempt is
200
- # made. If the library cannot be loaded either way, an error() is generated
201
- # and build_report() will not be called.
318
+ # Returns +true+ if a library can be loaded. A bare require() is made as
319
+ # the first attempt. If that fails, RubyGems is loaded and another attempt
320
+ # is made. If the library cannot be loaded either way, an error() is
321
+ # generated and build_report() will not be called.
202
322
  #
203
323
  def library_available?(library)
204
324
  begin
@@ -214,7 +334,40 @@ module ScoutAgent
214
334
  end
215
335
  true
216
336
  end
337
+
338
+ #
339
+ # Builds the data to send to the server. The public interface to this data
340
+ # lives in the helper methods: reports(), report(), add_report(), hints(),
341
+ # hint(), add_hint(), alerts(), alert(), add_alert(), errors(), error(), and
342
+ # add_error().
343
+ #
344
+ def data_for_server
345
+ @data_for_server ||= { :reports => Array.new,
346
+ :hints => Array.new,
347
+ :alerts => Array.new,
348
+ :errors => Array.new,
349
+ :memory => Hash.new }
350
+ end
217
351
 
352
+ #
353
+ # Builds a new alert or error Hash either by passing a Hash directly through
354
+ # or by grabbing a subject and body out of an Array of arguments.
355
+ #
356
+ def build_alert_or_error(new_alert_or_error)
357
+ if new_alert_or_error.first.is_a? Hash
358
+ new_alert_or_error
359
+ else
360
+ {:subject => new_alert_or_error.first, :body => new_alert_or_error.last}
361
+ end
362
+ end
363
+
364
+ #
365
+ # Writes generated reports and such to both the Mission database and the
366
+ # log(). The Mission memory will also be updated with the values stored
367
+ # from the execution that has just finished.
368
+ #
369
+ # Both database writes are skipped when running in the agent's test mode.
370
+ #
218
371
  def write_reports_and_update_memory
219
372
  %w[report hint alert error].each do |type|
220
373
  Array(send("#{type}s")).each do |fields|
@@ -228,6 +381,6 @@ module ScoutAgent
228
381
  end
229
382
  end
230
383
 
231
- # An external alias.
384
+ # An alias to support older plugins.
232
385
  Plugin = Mission
233
386
  end
@@ -32,8 +32,8 @@ module ScoutAgent
32
32
  end
33
33
 
34
34
  #
35
- # Hooks into Ruby's class load process so we can notice subclasses as they
36
- # are loaded.
35
+ # Hooks into Ruby's Class loading process so we can notice subclasses as
36
+ # they are loaded.
37
37
  #
38
38
  def self.inherited(order)
39
39
  subclasses << order
@@ -3,14 +3,17 @@
3
3
 
4
4
  module ScoutAgent
5
5
  #
6
- # This constant holds all global configuration for the agent. There's only
7
- # this one instance of this data and all systems share it. This data is
8
- # configured at startup and should never again change without a restart.
6
+ # These extra methods are mixed into the OpenStruct stored in
7
+ # ScoutAgent::Plan. They add support for defaults, some interface
8
+ # improvements, support for configuration files and command-line switches,
9
+ # plus validation.
9
10
  #
10
- # Users are free to add to this configuration as need (via their configuration
11
- # file) and make use of those new values in their plugins.
11
+ # To see the options stored in the plan and their current values, use the
12
+ # following command:
12
13
  #
13
- class << Plan = OpenStruct.new
14
+ # scout_agent config
15
+ #
16
+ module Planned
14
17
  ################
15
18
  ### Defaults ###
16
19
  ################
@@ -39,31 +42,44 @@ module ScoutAgent
39
42
  [:log_dir, nil] ]
40
43
  end
41
44
 
42
- # This method is used to set or reset the default configuration.
45
+ #
46
+ # This method is used to set or reset the default configuration. It returns
47
+ # +self+ to support method chaining.
48
+ #
43
49
  def set_defaults
44
50
  defaults.each do |name, value|
45
51
  send("#{name}=", value)
46
52
  end
53
+ self # for chaining
47
54
  end
48
55
  alias_method :reset_defaults, :set_defaults
49
56
 
50
- # Provide a more natural interface for a boolean flag.
51
- def run_as_daemon? # can't use alias_method() because it's not defined
57
+ #
58
+ # --
59
+ #
60
+ # The following interface enhancements cannot use alias_method() because the
61
+ # underlying method are not defined in OpenStruct until their first use.
62
+ #
63
+ # ++
64
+ #
65
+
66
+ # Provide a more natural interface for ScoutAgent::Plan#run_as_daemon.
67
+ def run_as_daemon?
52
68
  run_as_daemon
53
69
  end
54
70
 
55
- # Provide a more natural interface for a boolean flag.
56
- def test_mode? # can't use alias_method() because it's not defined
71
+ # Provide a more natural interface for ScoutAgent::Plan#test_mode.
72
+ def test_mode?
57
73
  test_mode
58
74
  end
59
75
 
60
- # Provide a more natural interface for a boolean flag.
61
- def enable_xmpp? # can't use alias_method() because it's not defined
76
+ # Provide a more natural interface for ScoutAgent::Plan#enable_xmpp.
77
+ def enable_xmpp?
62
78
  enable_xmpp
63
79
  end
64
80
 
65
- # Provide a more natural interface for a boolean flag.
66
- def periodic_snapshots? # can't use alias_method() because it's not defined
81
+ # Provide a more natural interface for ScoutAgent::Plan#periodic_snapshots.
82
+ def periodic_snapshots?
67
83
  periodic_snapshots
68
84
  end
69
85
 
@@ -386,9 +402,13 @@ module ScoutAgent
386
402
  end
387
403
  end
388
404
 
405
+ #
406
+ # This constant holds all global configuration for the agent. There's only
407
+ # this one instance of this data and all systems share it. This data is
408
+ # configured at startup and should never again change without a restart.
389
409
  #
390
- # Set the defaults after we have overriden some accessors, to keep OpenStruct
391
- # from replacing them.
410
+ # Users are free to add to this configuration as need (via their configuration
411
+ # file) and make use of those new values in their plugins.
392
412
  #
393
- Plan.set_defaults
413
+ Plan = OpenStruct.new.extend(Planned).set_defaults
394
414
  end