portertech-sensu 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +961 -0
- data/MIT-LICENSE.txt +20 -0
- data/README.md +65 -0
- data/exe/sensu-api +10 -0
- data/exe/sensu-client +10 -0
- data/exe/sensu-install +195 -0
- data/exe/sensu-server +10 -0
- data/lib/sensu/api/http_handler.rb +434 -0
- data/lib/sensu/api/process.rb +79 -0
- data/lib/sensu/api/routes/aggregates.rb +196 -0
- data/lib/sensu/api/routes/checks.rb +44 -0
- data/lib/sensu/api/routes/clients.rb +171 -0
- data/lib/sensu/api/routes/events.rb +86 -0
- data/lib/sensu/api/routes/health.rb +45 -0
- data/lib/sensu/api/routes/info.rb +37 -0
- data/lib/sensu/api/routes/request.rb +44 -0
- data/lib/sensu/api/routes/resolve.rb +32 -0
- data/lib/sensu/api/routes/results.rb +153 -0
- data/lib/sensu/api/routes/settings.rb +23 -0
- data/lib/sensu/api/routes/silenced.rb +182 -0
- data/lib/sensu/api/routes/stashes.rb +107 -0
- data/lib/sensu/api/routes.rb +88 -0
- data/lib/sensu/api/utilities/filter_response_content.rb +44 -0
- data/lib/sensu/api/utilities/publish_check_request.rb +107 -0
- data/lib/sensu/api/utilities/publish_check_result.rb +39 -0
- data/lib/sensu/api/utilities/resolve_event.rb +29 -0
- data/lib/sensu/api/utilities/servers_info.rb +43 -0
- data/lib/sensu/api/utilities/transport_info.rb +43 -0
- data/lib/sensu/api/validators/check.rb +55 -0
- data/lib/sensu/api/validators/client.rb +35 -0
- data/lib/sensu/api/validators/invalid.rb +8 -0
- data/lib/sensu/cli.rb +69 -0
- data/lib/sensu/client/http_socket.rb +217 -0
- data/lib/sensu/client/process.rb +655 -0
- data/lib/sensu/client/socket.rb +207 -0
- data/lib/sensu/client/utils.rb +53 -0
- data/lib/sensu/client/validators/check.rb +53 -0
- data/lib/sensu/constants.rb +17 -0
- data/lib/sensu/daemon.rb +396 -0
- data/lib/sensu/sandbox.rb +19 -0
- data/lib/sensu/server/filter.rb +227 -0
- data/lib/sensu/server/handle.rb +201 -0
- data/lib/sensu/server/mutate.rb +92 -0
- data/lib/sensu/server/process.rb +1646 -0
- data/lib/sensu/server/socket.rb +54 -0
- data/lib/sensu/server/tessen.rb +170 -0
- data/lib/sensu/utilities.rb +398 -0
- data/lib/sensu.rb +3 -0
- data/sensu.gemspec +36 -0
- metadata +322 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
module Sensu
|
2
|
+
module Server
|
3
|
+
class Socket < EM::Connection
|
4
|
+
# @!attribute [rw] on_success
|
5
|
+
# @return [Proc] callback to be called after the data has been
|
6
|
+
# transmitted successfully.
|
7
|
+
attr_accessor :on_success
|
8
|
+
|
9
|
+
# @!attribute [rw] on_error
|
10
|
+
# @return [Proc] callback to be called when there is an error.
|
11
|
+
attr_accessor :on_error
|
12
|
+
|
13
|
+
# Create a timeout timer to immediately close the socket
|
14
|
+
# connection and set `@timed_out` to true to indicate that the
|
15
|
+
# timeout caused the connection to close. The timeout timer is
|
16
|
+
# stored with `@timeout_timer`, so that it can be cancelled when
|
17
|
+
# the connection is closed.
|
18
|
+
#
|
19
|
+
# @param timeout [Numeric] in seconds.
|
20
|
+
def set_timeout(timeout)
|
21
|
+
@timeout_timer = EM::Timer.new(timeout) do
|
22
|
+
@timed_out = true
|
23
|
+
close_connection
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Record the current time when connected.
|
28
|
+
def connection_completed
|
29
|
+
@connected_at = Time.now.to_f
|
30
|
+
end
|
31
|
+
|
32
|
+
# Determine if the connection and data transmission was
|
33
|
+
# successful and call the appropriate callback, `@on_success`
|
34
|
+
# or `@on_error`, providing it with a message. Cancel the
|
35
|
+
# connection timeout timer `@timeout_timer`, if it is set. The
|
36
|
+
# `@connected_at` timestamp indicates that the connection was
|
37
|
+
# successful. If `@timed_out` is true, the connection was closed
|
38
|
+
# by the connection timeout, and the data is assumed to not have
|
39
|
+
# been transmitted.
|
40
|
+
def unbind
|
41
|
+
@timeout_timer.cancel if @timeout_timer
|
42
|
+
if @connected_at
|
43
|
+
if @timed_out
|
44
|
+
@on_error.call("socket timeout")
|
45
|
+
else
|
46
|
+
@on_success.call("wrote to socket")
|
47
|
+
end
|
48
|
+
else
|
49
|
+
@on_error.call("failed to connect to socket")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
gem "em-http-request", "1.1.5"
|
2
|
+
|
3
|
+
require "em-http-request"
|
4
|
+
require "sensu/constants"
|
5
|
+
|
6
|
+
module Sensu
|
7
|
+
module Server
|
8
|
+
class Tessen
|
9
|
+
attr_accessor :settings, :logger, :redis, :options
|
10
|
+
attr_reader :timers
|
11
|
+
|
12
|
+
# Create a new instance of Tessen. The instance variable
|
13
|
+
# `@timers` is use to track EventMachine timers for
|
14
|
+
# stopping/shutdown.
|
15
|
+
#
|
16
|
+
# @param options [Hash] containing the Sensu server Settings,
|
17
|
+
# Logger, and Redis connection.
|
18
|
+
def initialize(options={})
|
19
|
+
@timers = []
|
20
|
+
@settings = options[:settings]
|
21
|
+
@logger = options[:logger]
|
22
|
+
@redis = options[:redis]
|
23
|
+
@options = @settings.to_hash.fetch(:tessen, {})
|
24
|
+
end
|
25
|
+
|
26
|
+
# Determine if Tessen is enabled (opt-in).
|
27
|
+
#
|
28
|
+
# @return [TrueClass, FalseClass]
|
29
|
+
def enabled?
|
30
|
+
enabled = @options[:enabled] == true
|
31
|
+
unless enabled
|
32
|
+
note = "tessen collects anonymized data to help inform the sensu team about installations"
|
33
|
+
note << " - you can opt-in via configuration: {\"tessen\": {\"enabled\": true}}"
|
34
|
+
@logger.info("the tessen call-home mechanism is not enabled", :note => note)
|
35
|
+
end
|
36
|
+
enabled
|
37
|
+
end
|
38
|
+
|
39
|
+
# Run Tessen, scheduling data reports (every 6h).
|
40
|
+
def run
|
41
|
+
schedule_data_reports
|
42
|
+
end
|
43
|
+
|
44
|
+
# Stop Tessen, cancelling and clearing timers.
|
45
|
+
def stop
|
46
|
+
@timers.each do |timer|
|
47
|
+
timer.cancel
|
48
|
+
end
|
49
|
+
@timers.clear
|
50
|
+
end
|
51
|
+
|
52
|
+
# Schedule data reports, sending data to the Tessen service
|
53
|
+
# immediately and then every 6 hours after that.
|
54
|
+
def schedule_data_reports
|
55
|
+
send_data
|
56
|
+
@timers << EM::PeriodicTimer.new(21600) do
|
57
|
+
send_data
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Send data to the Tessen service.
|
62
|
+
def send_data(&block)
|
63
|
+
create_data do |data|
|
64
|
+
tessen_api_request(data, &block)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Create data to be sent to the Tessen service.
|
69
|
+
#
|
70
|
+
# @return [Hash]
|
71
|
+
def create_data
|
72
|
+
get_install_id do |install_id|
|
73
|
+
get_client_count do |client_count|
|
74
|
+
get_server_count do |server_count|
|
75
|
+
identity_key = @options.fetch(:identity_key, "")
|
76
|
+
flavour, version = get_version_info
|
77
|
+
timestamp = Time.now.to_i
|
78
|
+
data = {
|
79
|
+
:tessen_identity_key => identity_key,
|
80
|
+
:install => {
|
81
|
+
:id => install_id,
|
82
|
+
:sensu_flavour => flavour,
|
83
|
+
:sensu_version => version
|
84
|
+
},
|
85
|
+
:metrics => {
|
86
|
+
:points => [
|
87
|
+
{
|
88
|
+
:name => "client_count",
|
89
|
+
:value => client_count,
|
90
|
+
:timestamp => timestamp
|
91
|
+
},
|
92
|
+
{
|
93
|
+
:name => "server_count",
|
94
|
+
:value => server_count,
|
95
|
+
:timestamp => timestamp
|
96
|
+
}
|
97
|
+
]
|
98
|
+
}
|
99
|
+
}
|
100
|
+
yield data
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Get the Sensu installation ID. The ID is randomly generated
|
107
|
+
# and stored in Redis. This ID provides context and allows
|
108
|
+
# multiple Sensu servers to report data for the same installation.
|
109
|
+
def get_install_id
|
110
|
+
@redis.setnx("tessen:install_id", rand(36**12).to_s(36)) do |created|
|
111
|
+
@redis.get("tessen:install_id") do |install_id|
|
112
|
+
yield install_id
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Get the Sensu client count for the installation. This count
|
118
|
+
# currently includes proxy clients.
|
119
|
+
#
|
120
|
+
# @yield [count]
|
121
|
+
# @yieldparam [Integer] client count
|
122
|
+
def get_client_count
|
123
|
+
@redis.scard("clients") do |count|
|
124
|
+
yield count.to_i
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Get the Sensu server count for the installation.
|
129
|
+
#
|
130
|
+
# @yield [count]
|
131
|
+
# @yieldparam [Integer] server count
|
132
|
+
def get_server_count
|
133
|
+
@redis.scard("servers") do |count|
|
134
|
+
yield count.to_i
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Get the Sensu version info for the local Sensu service.
|
139
|
+
def get_version_info
|
140
|
+
if defined?(Sensu::Enterprise::VERSION)
|
141
|
+
["enterprise", Sensu::Enterprise::VERSION]
|
142
|
+
else
|
143
|
+
["core", Sensu::VERSION]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Make a Tessen service API request.
|
148
|
+
#
|
149
|
+
# @param data [Hash]
|
150
|
+
def tessen_api_request(data)
|
151
|
+
@logger.debug("sending data to the tessen call-home service", {
|
152
|
+
:data => data,
|
153
|
+
:options => @options
|
154
|
+
})
|
155
|
+
connection = {}
|
156
|
+
connection[:proxy] = @options[:proxy] if @options[:proxy]
|
157
|
+
post_options = {:body => Sensu::JSON.dump(data)}
|
158
|
+
http = EM::HttpRequest.new("https://tessen.sensu.io/v1/data", connection).post(post_options)
|
159
|
+
http.callback do
|
160
|
+
@logger.debug("tessen call-home service response", :status => http.response_header.status)
|
161
|
+
yield if block_given?
|
162
|
+
end
|
163
|
+
http.errback do
|
164
|
+
@logger.debug("tessen call-home service error", :error => http.error)
|
165
|
+
yield if block_given?
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,398 @@
|
|
1
|
+
gem "parse-cron", "0.1.4"
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
require "sensu/sandbox"
|
5
|
+
require "parse-cron"
|
6
|
+
require "socket"
|
7
|
+
|
8
|
+
module Sensu
|
9
|
+
module Utilities
|
10
|
+
EVAL_PREFIX = "eval:".freeze
|
11
|
+
NANOSECOND_RESOLUTION = 9.freeze
|
12
|
+
|
13
|
+
# Determine if Sensu is being tested, using the process name.
|
14
|
+
# Sensu is being test if the process name is "rspec",
|
15
|
+
#
|
16
|
+
# @return [TrueClass, FalseClass]
|
17
|
+
def testing?
|
18
|
+
File.basename($0) == "rspec"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Retry a code block until it retures true. The first attempt and
|
22
|
+
# following retries are delayed.
|
23
|
+
#
|
24
|
+
# @param wait [Numeric] time to delay block calls.
|
25
|
+
# @param block [Proc] to call that needs to return true.
|
26
|
+
def retry_until_true(wait=0.5, &block)
|
27
|
+
EM::Timer.new(wait) do
|
28
|
+
unless block.call
|
29
|
+
retry_until_true(wait, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Deep merge two hashes. Nested hashes are deep merged, arrays are
|
35
|
+
# concatenated and duplicate array items are removed.
|
36
|
+
#
|
37
|
+
# @param hash_one [Hash]
|
38
|
+
# @param hash_two [Hash]
|
39
|
+
# @return [Hash] deep merged hash.
|
40
|
+
def deep_merge(hash_one, hash_two)
|
41
|
+
merged = hash_one.dup
|
42
|
+
hash_two.each do |key, value|
|
43
|
+
merged[key] = case
|
44
|
+
when hash_one[key].is_a?(Hash) && value.is_a?(Hash)
|
45
|
+
deep_merge(hash_one[key], value)
|
46
|
+
when hash_one[key].is_a?(Array) && value.is_a?(Array)
|
47
|
+
(hash_one[key] + value).uniq
|
48
|
+
else
|
49
|
+
value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
merged
|
53
|
+
end
|
54
|
+
|
55
|
+
# Creates a deep dup of basic ruby objects with support for walking
|
56
|
+
# hashes and arrays.
|
57
|
+
#
|
58
|
+
# @param obj [Object]
|
59
|
+
# @return [obj] a dup of the original object.
|
60
|
+
def deep_dup(obj)
|
61
|
+
if obj.class == Hash
|
62
|
+
new_obj = obj.dup
|
63
|
+
new_obj.each do |key, value|
|
64
|
+
new_obj[deep_dup(key)] = deep_dup(value)
|
65
|
+
end
|
66
|
+
new_obj
|
67
|
+
elsif obj.class == Array
|
68
|
+
arr = []
|
69
|
+
obj.each do |item|
|
70
|
+
arr << deep_dup(item)
|
71
|
+
end
|
72
|
+
arr
|
73
|
+
elsif obj.class == String
|
74
|
+
obj.dup
|
75
|
+
else
|
76
|
+
obj
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Retrieve the system hostname. If the hostname cannot be
|
81
|
+
# determined and an error is thrown, `nil` will be returned.
|
82
|
+
#
|
83
|
+
# @return [String] system hostname.
|
84
|
+
def system_hostname
|
85
|
+
::Socket.gethostname rescue nil
|
86
|
+
end
|
87
|
+
|
88
|
+
# Retrieve the system IP address. If a valid non-loopback
|
89
|
+
# IPv4 address cannot be found and an error is thrown,
|
90
|
+
# `nil` will be returned.
|
91
|
+
#
|
92
|
+
# @return [String] system ip address
|
93
|
+
def system_address
|
94
|
+
::Socket.ip_address_list.find { |address|
|
95
|
+
address.ipv4? && !address.ipv4_loopback?
|
96
|
+
}.ip_address rescue nil
|
97
|
+
end
|
98
|
+
|
99
|
+
# Retrieve the process CPU times. If the cpu times cannot be
|
100
|
+
# determined and an error is thrown, `[nil, nil, nil, nil]` will
|
101
|
+
# be returned.
|
102
|
+
#
|
103
|
+
# @return [Array] CPU times: utime, stime, cutime, cstime
|
104
|
+
def process_cpu_times(&callback)
|
105
|
+
determine_cpu_times = Proc.new do
|
106
|
+
::Process.times.to_a rescue [nil, nil, nil, nil]
|
107
|
+
end
|
108
|
+
EM::defer(determine_cpu_times, callback)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Generate a random universally unique identifier.
|
112
|
+
#
|
113
|
+
# @return [String] random UUID.
|
114
|
+
def random_uuid
|
115
|
+
::SecureRandom.uuid
|
116
|
+
end
|
117
|
+
|
118
|
+
# Remove sensitive information from a hash (eg. passwords). By
|
119
|
+
# default, hash values will be redacted for the following keys:
|
120
|
+
# password, passwd, pass, api_key, api_token, access_key,
|
121
|
+
# secret_key, private_key, secret
|
122
|
+
#
|
123
|
+
# @param obj [Object] to redact sensitive value from.
|
124
|
+
# @param keys [Array] that indicate sensitive values.
|
125
|
+
# @return [Hash] hash with redacted sensitive values.
|
126
|
+
def redact_sensitive(obj, keys=nil)
|
127
|
+
keys ||= %w[
|
128
|
+
password passwd pass
|
129
|
+
api_key api_token
|
130
|
+
access_key secret_key private_key
|
131
|
+
secret
|
132
|
+
routing_key
|
133
|
+
access_token_read access_token_write access_token_path
|
134
|
+
webhook_url
|
135
|
+
nickserv_password channel_password
|
136
|
+
community
|
137
|
+
keystore_password truststore_password
|
138
|
+
proxy_password
|
139
|
+
access_key_id secret_access_key
|
140
|
+
]
|
141
|
+
obj = obj.dup
|
142
|
+
if obj.is_a?(Hash)
|
143
|
+
obj.each do |key, value|
|
144
|
+
if keys.include?(key.to_s)
|
145
|
+
obj[key] = "REDACTED"
|
146
|
+
elsif value.is_a?(Hash) || value.is_a?(Array)
|
147
|
+
obj[key] = redact_sensitive(value, keys)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
elsif obj.is_a?(Array)
|
151
|
+
obj.map! do |item|
|
152
|
+
if item.is_a?(Hash) || item.is_a?(Array)
|
153
|
+
redact_sensitive(item, keys)
|
154
|
+
else
|
155
|
+
item
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
obj
|
160
|
+
end
|
161
|
+
|
162
|
+
# Traverse a hash for an attribute value, with a fallback default
|
163
|
+
# value if nil.
|
164
|
+
#
|
165
|
+
# @param tree [Hash] to traverse.
|
166
|
+
# @param path [Array] of attribute keys.
|
167
|
+
# @param default [Object] value if attribute value is nil.
|
168
|
+
# @return [Object] attribute or fallback default value.
|
169
|
+
def find_attribute_value(tree, path, default)
|
170
|
+
attribute = tree[path.shift]
|
171
|
+
if attribute.is_a?(Hash)
|
172
|
+
find_attribute_value(attribute, path, default)
|
173
|
+
else
|
174
|
+
attribute.nil? ? default : attribute
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Substitute dot notation tokens (eg. :::db.name|production:::)
|
179
|
+
# with the associated definition attribute value. Tokens can
|
180
|
+
# provide a fallback default value, following a pipe.
|
181
|
+
#
|
182
|
+
# @param tokens [String]
|
183
|
+
# @param attributes [Hash]
|
184
|
+
# @return [Array] containing the string with tokens substituted
|
185
|
+
# and an array of unmatched tokens.
|
186
|
+
def substitute_tokens(tokens, attributes)
|
187
|
+
unmatched_tokens = []
|
188
|
+
encoded_tokens = tokens.encode("UTF-8", "binary", **{
|
189
|
+
:invalid => :replace,
|
190
|
+
:undef => :replace,
|
191
|
+
:replace => ""
|
192
|
+
})
|
193
|
+
substituted = encoded_tokens.gsub(/:::([^:].*?):::/) do
|
194
|
+
token, default = $1.to_s.split("|", 2)
|
195
|
+
path = token.split(".").map(&:to_sym)
|
196
|
+
matched = find_attribute_value(attributes, path, default)
|
197
|
+
if matched.nil?
|
198
|
+
unmatched_tokens << token
|
199
|
+
end
|
200
|
+
matched
|
201
|
+
end
|
202
|
+
[substituted, unmatched_tokens]
|
203
|
+
end
|
204
|
+
|
205
|
+
# Perform token substitution for an object. String values are
|
206
|
+
# passed to `substitute_tokens()`, arrays and sub-hashes are
|
207
|
+
# processed recursively. Numeric values are ignored.
|
208
|
+
#
|
209
|
+
# @param object [Object]
|
210
|
+
# @param attributes [Hash]
|
211
|
+
# @return [Array] containing the updated object with substituted
|
212
|
+
# values and an array of unmatched tokens.
|
213
|
+
def object_substitute_tokens(object, attributes)
|
214
|
+
unmatched_tokens = []
|
215
|
+
case object
|
216
|
+
when Hash
|
217
|
+
object.each do |key, value|
|
218
|
+
object[key], unmatched = object_substitute_tokens(value, attributes)
|
219
|
+
unmatched_tokens.push(*unmatched)
|
220
|
+
end
|
221
|
+
when Array
|
222
|
+
object.map! do |value|
|
223
|
+
value, unmatched = object_substitute_tokens(value, attributes)
|
224
|
+
unmatched_tokens.push(*unmatched)
|
225
|
+
value
|
226
|
+
end
|
227
|
+
when String
|
228
|
+
object, unmatched_tokens = substitute_tokens(object, attributes)
|
229
|
+
end
|
230
|
+
[object, unmatched_tokens.uniq]
|
231
|
+
end
|
232
|
+
|
233
|
+
# Process an eval attribute value, a Ruby `eval()` string
|
234
|
+
# containing an expression to be evaluated within the
|
235
|
+
# scope/context of a sandbox. This methods strips away the
|
236
|
+
# expression prefix, `eval:`, and substitues any dot notation
|
237
|
+
# tokens with the corresponding event data values. If there are
|
238
|
+
# unmatched tokens, this method will return `nil`.
|
239
|
+
#
|
240
|
+
# @object [Hash]
|
241
|
+
# @raw_eval_string [String]
|
242
|
+
# @return [String] processed eval string.
|
243
|
+
def process_eval_string(object, raw_eval_string)
|
244
|
+
eval_string = raw_eval_string.slice(5..-1)
|
245
|
+
eval_string, unmatched_tokens = substitute_tokens(eval_string, object)
|
246
|
+
if unmatched_tokens.empty?
|
247
|
+
eval_string
|
248
|
+
else
|
249
|
+
@logger.error("attribute value eval unmatched tokens", {
|
250
|
+
:object => object,
|
251
|
+
:raw_eval_string => raw_eval_string,
|
252
|
+
:unmatched_tokens => unmatched_tokens
|
253
|
+
})
|
254
|
+
nil
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Ruby `eval()` a string containing an expression, within the
|
259
|
+
# scope/context of a sandbox. This method is for attribute values
|
260
|
+
# starting with "eval:", with the Ruby expression following the
|
261
|
+
# colon. A single variable is provided to the expression, `value`,
|
262
|
+
# equal to the corresponding object attribute value. Dot notation
|
263
|
+
# tokens in the expression, e.g. `:::mysql.user:::`, are
|
264
|
+
# substituted with the corresponding object attribute values prior
|
265
|
+
# to evaluation. The expression is expected to return a boolean
|
266
|
+
# value.
|
267
|
+
#
|
268
|
+
# @param object [Hash]
|
269
|
+
# @param raw_eval_string [String] containing the Ruby
|
270
|
+
# expression to be evaluated.
|
271
|
+
# @param raw_value [Object] of the corresponding object
|
272
|
+
# attribute value.
|
273
|
+
# @return [TrueClass, FalseClass]
|
274
|
+
def eval_attribute_value(object, raw_eval_string, raw_value)
|
275
|
+
eval_string = process_eval_string(object, raw_eval_string)
|
276
|
+
unless eval_string.nil?
|
277
|
+
begin
|
278
|
+
value = Marshal.load(Marshal.dump(raw_value))
|
279
|
+
!!Sandbox.eval(eval_string, value)
|
280
|
+
rescue StandardError, SyntaxError => error
|
281
|
+
@logger.error("attribute value eval error", {
|
282
|
+
:object => object,
|
283
|
+
:raw_eval_string => raw_eval_string,
|
284
|
+
:raw_value => raw_value,
|
285
|
+
:error => error.to_s
|
286
|
+
})
|
287
|
+
false
|
288
|
+
end
|
289
|
+
else
|
290
|
+
false
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Determine if all attribute values match those of the
|
295
|
+
# corresponding object attributes. Attributes match if the value
|
296
|
+
# objects are equivalent, are both hashes with matching key/value
|
297
|
+
# pairs (recursive), have equal string values, or evaluate to true
|
298
|
+
# (Ruby eval).
|
299
|
+
#
|
300
|
+
# @param object [Hash]
|
301
|
+
# @param match_attributes [Object]
|
302
|
+
# @param support_eval [TrueClass, FalseClass]
|
303
|
+
# @param object_attributes [Object]
|
304
|
+
# @return [TrueClass, FalseClass]
|
305
|
+
def attributes_match?(object, match_attributes, support_eval=true, object_attributes=nil)
|
306
|
+
object_attributes ||= object
|
307
|
+
match_attributes.all? do |key, value_one|
|
308
|
+
value_two = object_attributes[key]
|
309
|
+
case
|
310
|
+
when value_one == value_two
|
311
|
+
true
|
312
|
+
when value_one.is_a?(Hash) && value_two.is_a?(Hash)
|
313
|
+
attributes_match?(object, value_one, support_eval, value_two)
|
314
|
+
when value_one.to_s == value_two.to_s
|
315
|
+
true
|
316
|
+
when value_one.is_a?(String) && value_one.start_with?(EVAL_PREFIX) && support_eval
|
317
|
+
eval_attribute_value(object, value_one, value_two)
|
318
|
+
else
|
319
|
+
false
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# Determine if the current time falls within a time window. The
|
325
|
+
# provided condition must have a `:begin` and `:end` time, eg.
|
326
|
+
# "11:30:00 PM", or `false` will be returned.
|
327
|
+
#
|
328
|
+
# @param condition [Hash]
|
329
|
+
# @option condition [String] :begin time.
|
330
|
+
# @option condition [String] :end time.
|
331
|
+
# @return [TrueClass, FalseClass]
|
332
|
+
def in_time_window?(condition)
|
333
|
+
if condition.has_key?(:begin) && condition.has_key?(:end)
|
334
|
+
begin_time = Time.parse(condition[:begin])
|
335
|
+
end_time = Time.parse(condition[:end])
|
336
|
+
if end_time < begin_time
|
337
|
+
if Time.now < end_time
|
338
|
+
begin_time = Time.parse(*begin_time.strftime("%Y-%m-%d 00:00:00.#{Array.new(NANOSECOND_RESOLUTION, 0).join} %:z"))
|
339
|
+
else
|
340
|
+
end_time = Time.parse(*end_time.strftime("%Y-%m-%d 23:59:59.#{Array.new(NANOSECOND_RESOLUTION, 9).join} %:z"))
|
341
|
+
end
|
342
|
+
end
|
343
|
+
Time.now >= begin_time && Time.now <= end_time
|
344
|
+
else
|
345
|
+
false
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# Determine if time window conditions for one or more days of the
|
350
|
+
# week are met. If a day of the week is provided, it can provide
|
351
|
+
# one or more conditions, each with a `:begin` and `:end` time,
|
352
|
+
# eg. "11:30:00 PM", or `false` will be returned.
|
353
|
+
#
|
354
|
+
# @param conditions [Hash]
|
355
|
+
# @option conditions [String] :days of the week.
|
356
|
+
# @return [TrueClass, FalseClass]
|
357
|
+
def in_time_windows?(conditions)
|
358
|
+
in_window = false
|
359
|
+
window_days = conditions[:days] || {}
|
360
|
+
if window_days[:all]
|
361
|
+
in_window = window_days[:all].any? do |condition|
|
362
|
+
in_time_window?(condition)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
current_day = Time.now.strftime("%A").downcase.to_sym
|
366
|
+
if !in_window && window_days[current_day]
|
367
|
+
in_window = window_days[current_day].any? do |condition|
|
368
|
+
in_time_window?(condition)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
in_window
|
372
|
+
end
|
373
|
+
|
374
|
+
# Determine if a check is subdued, by conditions set in the check
|
375
|
+
# definition. If any of the conditions are true, without an
|
376
|
+
# exception, the check is subdued.
|
377
|
+
#
|
378
|
+
# @param check [Hash] definition.
|
379
|
+
# @return [TrueClass, FalseClass]
|
380
|
+
def check_subdued?(check)
|
381
|
+
if check[:subdue]
|
382
|
+
in_time_windows?(check[:subdue])
|
383
|
+
else
|
384
|
+
false
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
# Determine the next check cron time.
|
389
|
+
#
|
390
|
+
# @param check [Hash] definition.
|
391
|
+
def determine_check_cron_time(check)
|
392
|
+
cron_parser = CronParser.new(check[:cron])
|
393
|
+
current_time = Time.now
|
394
|
+
next_cron_time = cron_parser.next(current_time)
|
395
|
+
next_cron_time - current_time
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
data/lib/sensu.rb
ADDED
data/sensu.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.join(File.dirname(__FILE__), "lib", "sensu", "constants")
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "portertech-sensu"
|
6
|
+
s.version = Sensu::VERSION
|
7
|
+
s.authors = ["Sean Porter", "Justin Kolberg"]
|
8
|
+
s.email = ["portertech@gmail.com", "amdprophet@gmail.com"]
|
9
|
+
s.homepage = "https://sensu.io"
|
10
|
+
s.summary = "A monitoring framework"
|
11
|
+
s.description = "A monitoring framework that aims to be simple, malleable, and scalable."
|
12
|
+
s.license = "MIT"
|
13
|
+
|
14
|
+
s.add_dependency "eventmachine", "1.2.7"
|
15
|
+
s.add_dependency "portertech-sensu-json", "2.2.1"
|
16
|
+
s.add_dependency "portertech-sensu-logger", "1.4.0"
|
17
|
+
s.add_dependency "portertech-sensu-settings", "10.18.0"
|
18
|
+
s.add_dependency "sensu-extension", "1.5.2"
|
19
|
+
s.add_dependency "portertech-sensu-extensions", "1.12.0"
|
20
|
+
s.add_dependency "sensu-transport", "8.3.0"
|
21
|
+
s.add_dependency "portertech-sensu-spawn", "2.6.1"
|
22
|
+
s.add_dependency "sensu-redis", "2.4.0"
|
23
|
+
s.add_dependency "em-http-server", "0.1.8"
|
24
|
+
s.add_dependency "em-http-request", "1.1.5"
|
25
|
+
s.add_dependency "parse-cron", "0.1.4"
|
26
|
+
|
27
|
+
s.add_development_dependency "rake", "13.0.6"
|
28
|
+
s.add_development_dependency "rspec", "~> 3.12.0"
|
29
|
+
s.add_development_dependency "addressable", "2.3.8"
|
30
|
+
s.add_development_dependency "webmock", "3.3.0"
|
31
|
+
|
32
|
+
s.files = Dir.glob("{exe,lib}/**/*") + %w[sensu.gemspec README.md CHANGELOG.md MIT-LICENSE.txt]
|
33
|
+
s.executables = s.files.grep(%r{^exe/}) { |file| File.basename(file) }
|
34
|
+
s.bindir = "exe"
|
35
|
+
s.require_paths = ["lib"]
|
36
|
+
end
|