portertech-sensu 1.10.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.
- 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
|