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,55 @@
|
|
1
|
+
require "sensu/api/validators/invalid"
|
2
|
+
require "sensu/settings/rules"
|
3
|
+
require "sensu/settings/validators/check"
|
4
|
+
|
5
|
+
module Sensu
|
6
|
+
module API
|
7
|
+
module Validators
|
8
|
+
class Check
|
9
|
+
# Include Sensu Settings rules and check validator.
|
10
|
+
include Sensu::Settings::Rules
|
11
|
+
include Sensu::Settings::Validators::Check
|
12
|
+
|
13
|
+
# Validate a check result, selectively using check definition
|
14
|
+
# validation methods.
|
15
|
+
#
|
16
|
+
# @param check [Hash]
|
17
|
+
def validate_check_result(check)
|
18
|
+
must_be_a_string(check[:output]) ||
|
19
|
+
invalid(check, "check output must be a string")
|
20
|
+
must_be_an_integer(check[:status]) ||
|
21
|
+
invalid(check, "check status must be an integer")
|
22
|
+
must_be_an_integer(check[:executed]) ||
|
23
|
+
invalid(check, "check executed timestamp must be an integer")
|
24
|
+
validate_check_name(check)
|
25
|
+
validate_check_handling(check)
|
26
|
+
validate_check_aggregate(check)
|
27
|
+
validate_check_flap_detection(check)
|
28
|
+
validate_check_truncate_output(check)
|
29
|
+
validate_check_source(check) if check[:source]
|
30
|
+
validate_check_ttl(check) if check[:ttl]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Determine if a check definition is valid.
|
34
|
+
#
|
35
|
+
# @param check [Hash]
|
36
|
+
# @return [TrueClass, FalseClass]
|
37
|
+
def valid?(check)
|
38
|
+
validate_check_result(check)
|
39
|
+
true
|
40
|
+
rescue Invalid
|
41
|
+
false
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# This method is called when validation methods encounter an
|
47
|
+
# invalid definition object. This method raises an exception
|
48
|
+
# to be caught by `valid?()`.
|
49
|
+
def invalid(*arguments)
|
50
|
+
raise Invalid
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "sensu/api/validators/invalid"
|
2
|
+
require "sensu/settings/rules"
|
3
|
+
require "sensu/settings/validators/client"
|
4
|
+
|
5
|
+
module Sensu
|
6
|
+
module API
|
7
|
+
module Validators
|
8
|
+
class Client
|
9
|
+
# Include Sensu Settings rules and client validator.
|
10
|
+
include Sensu::Settings::Rules
|
11
|
+
include Sensu::Settings::Validators::Client
|
12
|
+
|
13
|
+
# Determine if a client definition is valid.
|
14
|
+
#
|
15
|
+
# @param client [Hash]
|
16
|
+
# @return [TrueClass, FalseClass]
|
17
|
+
def valid?(client)
|
18
|
+
validate_client(client)
|
19
|
+
true
|
20
|
+
rescue Invalid
|
21
|
+
false
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# This method is called when `validate_client()` encounters an
|
27
|
+
# invalid definition object. This method raises an exception
|
28
|
+
# to be caught by `valid?()`.
|
29
|
+
def invalid(*arguments)
|
30
|
+
raise Invalid
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/sensu/cli.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require "optparse"
|
2
|
+
require "sensu/logger/constants"
|
3
|
+
|
4
|
+
module Sensu
|
5
|
+
class CLI
|
6
|
+
# Parse CLI arguments using Ruby stdlib `optparse`. This method
|
7
|
+
# provides Sensu with process options (eg. log file), and can
|
8
|
+
# provide users with information, such as the Sensu version.
|
9
|
+
#
|
10
|
+
# @param arguments [Array] to parse.
|
11
|
+
# @return [Hash] options
|
12
|
+
def self.read(arguments=ARGV)
|
13
|
+
options = {}
|
14
|
+
if File.exist?("/etc/sensu/config.json")
|
15
|
+
options[:config_file] = "/etc/sensu/config.json"
|
16
|
+
end
|
17
|
+
if Dir.exist?("/etc/sensu/conf.d")
|
18
|
+
options[:config_dirs] = ["/etc/sensu/conf.d"]
|
19
|
+
end
|
20
|
+
optparse = OptionParser.new do |opts|
|
21
|
+
opts.on("-h", "--help", "Display this message") do
|
22
|
+
puts opts
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
opts.on("-V", "--version", "Display version") do
|
26
|
+
puts VERSION
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
opts.on("-c", "--config FILE", "Sensu JSON config FILE. Default: /etc/sensu/config.json (if exists)") do |file|
|
30
|
+
options[:config_file] = file
|
31
|
+
end
|
32
|
+
opts.on("-d", "--config_dir DIR[,DIR]", "DIR or comma-delimited DIR list for Sensu JSON config files. Default: /etc/sensu/conf.d (if exists)") do |dir|
|
33
|
+
options[:config_dirs] = dir.split(",")
|
34
|
+
end
|
35
|
+
opts.on("--validate_config", "Validate the compiled configuration and exit") do
|
36
|
+
options[:validate_config] = true
|
37
|
+
end
|
38
|
+
opts.on("-P", "--print_config", "Print the compiled configuration and exit") do
|
39
|
+
options[:print_config] = true
|
40
|
+
end
|
41
|
+
opts.on("-e", "--extension_dir DIR", "DIR for Sensu extensions") do |dir|
|
42
|
+
options[:extension_dir] = dir
|
43
|
+
end
|
44
|
+
opts.on("-l", "--log FILE", "Log to a given FILE. Default: STDOUT") do |file|
|
45
|
+
options[:log_file] = file
|
46
|
+
end
|
47
|
+
opts.on("-L", "--log_level LEVEL", "Log severity LEVEL") do |level|
|
48
|
+
log_level = level.to_s.downcase.to_sym
|
49
|
+
unless Logger::LEVELS.include?(log_level)
|
50
|
+
puts "Unknown log level: #{level}"
|
51
|
+
exit 1
|
52
|
+
end
|
53
|
+
options[:log_level] = log_level
|
54
|
+
end
|
55
|
+
opts.on("-v", "--verbose", "Enable verbose logging") do
|
56
|
+
options[:log_level] = :debug
|
57
|
+
end
|
58
|
+
opts.on("-b", "--background", "Fork into the background") do
|
59
|
+
options[:daemonize] = true
|
60
|
+
end
|
61
|
+
opts.on("-p", "--pid_file FILE", "Write the PID to a given FILE") do |file|
|
62
|
+
options[:pid_file] = file
|
63
|
+
end
|
64
|
+
end
|
65
|
+
optparse.parse!(arguments)
|
66
|
+
options
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
require "base64"
|
2
|
+
require "em-http-server"
|
3
|
+
require "sensu/json"
|
4
|
+
require "sensu/utilities"
|
5
|
+
require "sensu/api/utilities/transport_info"
|
6
|
+
require "sensu/client/utils"
|
7
|
+
|
8
|
+
module Sensu
|
9
|
+
module Client
|
10
|
+
# EventMachine connection handler for the Sensu HTTP client's socket.
|
11
|
+
#
|
12
|
+
# The Sensu client listens on localhost, port 3031 (by default), for
|
13
|
+
# TCP HTTP connections. This allows software running on the host to
|
14
|
+
# push check results (that may contain metrics) into Sensu, without
|
15
|
+
# needing to know anything about Sensu's internal implementation.
|
16
|
+
#
|
17
|
+
# All requests and responses expect a json-encoded body (if a body
|
18
|
+
# is expected at all).
|
19
|
+
#
|
20
|
+
# This socket requires receiving a proper HTTP request to any of
|
21
|
+
# the following endpoints:
|
22
|
+
#
|
23
|
+
# GET /info
|
24
|
+
# This endpoint returns 200 OK with some basic Sensu info
|
25
|
+
#
|
26
|
+
# POST /results
|
27
|
+
# This endpoint expects application/json body with a check result
|
28
|
+
#
|
29
|
+
# GET /settings
|
30
|
+
# This endpoint responds with 200 OK and the Sensu configuration
|
31
|
+
#
|
32
|
+
# GET /brew
|
33
|
+
# This endpoint gets you some fresh coffee
|
34
|
+
class HTTPSocket < EM::HttpServer::Server
|
35
|
+
include Sensu::API::Utilities::TransportInfo
|
36
|
+
include Utilities
|
37
|
+
include CheckUtils
|
38
|
+
|
39
|
+
attr_accessor :logger, :settings, :transport
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
super
|
43
|
+
@endpoints = {
|
44
|
+
"/info" => {
|
45
|
+
"methods" => {
|
46
|
+
"GET" => method(:process_request_info)
|
47
|
+
},
|
48
|
+
"help" => "Sensu client information"
|
49
|
+
},
|
50
|
+
"/results" => {
|
51
|
+
"methods" => {
|
52
|
+
"POST" => method(:process_request_results)
|
53
|
+
},
|
54
|
+
"help" => "Send check JSON results here"
|
55
|
+
},
|
56
|
+
"/settings" => {
|
57
|
+
"methods" => {
|
58
|
+
"GET" => method(:process_request_settings)
|
59
|
+
},
|
60
|
+
"help" => "Get redacted Sensu settings (requires basic auth). Use ?redacted=false if you want the setting unredacted."
|
61
|
+
},
|
62
|
+
"/brew" => {
|
63
|
+
"methods" => {
|
64
|
+
"GET" => Proc.new { |response|
|
65
|
+
send_response(418, "I'm a teapot", {
|
66
|
+
:response => "I'm a teapot!"
|
67
|
+
})
|
68
|
+
}
|
69
|
+
},
|
70
|
+
"help" => "Ask Sensu to brew a cup of joe (try it!)"
|
71
|
+
}
|
72
|
+
}
|
73
|
+
@response = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def authorized?
|
77
|
+
http_options = @settings[:client][:http_socket] || Hash.new
|
78
|
+
if http_options[:user] and http_options[:password]
|
79
|
+
if @http[:authorization]
|
80
|
+
scheme, base64 = @http[:authorization].split("\s")
|
81
|
+
if scheme == "Basic"
|
82
|
+
user, password = Base64.decode64(base64).split(":")
|
83
|
+
return (user == http_options[:user] && password == http_options[:password])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
false
|
88
|
+
end
|
89
|
+
|
90
|
+
def unauthorized_response
|
91
|
+
@logger.warn("http socket refusing to serve unauthorized request")
|
92
|
+
@response.headers["WWW-Authenticate"] = 'Basic realm="Sensu Client Restricted Area"'
|
93
|
+
send_response(401, "Unauthorized", {
|
94
|
+
:response => "You must be authenticated using your http_options user and password settings"
|
95
|
+
})
|
96
|
+
end
|
97
|
+
|
98
|
+
def send_response(status, status_string, content)
|
99
|
+
@logger.debug("http socket sending response", {
|
100
|
+
:status => status,
|
101
|
+
:status_string => status_string,
|
102
|
+
:content => content
|
103
|
+
})
|
104
|
+
@response.status = status
|
105
|
+
@response.status_string = status_string
|
106
|
+
@response.content = Sensu::JSON::dump(content)
|
107
|
+
@response.send_response
|
108
|
+
end
|
109
|
+
|
110
|
+
def process_request_info
|
111
|
+
if @settings[:client][:http_socket] &&
|
112
|
+
@settings[:client][:http_socket][:protect_all_endpoints]
|
113
|
+
return unauthorized_response unless authorized?
|
114
|
+
end
|
115
|
+
transport_info do |info|
|
116
|
+
send_response(200, "OK", {
|
117
|
+
:sensu => {
|
118
|
+
:version => VERSION
|
119
|
+
},
|
120
|
+
:transport => info
|
121
|
+
})
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def process_request_results
|
126
|
+
if @settings[:client][:http_socket] &&
|
127
|
+
@settings[:client][:http_socket][:protect_all_endpoints]
|
128
|
+
return unauthorized_response unless authorized?
|
129
|
+
end
|
130
|
+
if @http[:content_type] and @http[:content_type].include?("application/json") and @http_content
|
131
|
+
begin
|
132
|
+
object = Sensu::JSON::load(@http_content)
|
133
|
+
if object.is_a?(Array)
|
134
|
+
object.each do |check|
|
135
|
+
process_check_result(check)
|
136
|
+
end
|
137
|
+
else
|
138
|
+
process_check_result(object)
|
139
|
+
end
|
140
|
+
send_response(202, "OK", {:response => "ok"})
|
141
|
+
rescue Sensu::JSON::ParseError, ArgumentError
|
142
|
+
send_response(400, "Failed to parse JSON body", {:response => "Failed to parse JSON body"})
|
143
|
+
end
|
144
|
+
else
|
145
|
+
send_response(415, "Only application/json content type accepted", {:response => "Invalid content type"})
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def process_request_settings
|
150
|
+
return unauthorized_response unless authorized?
|
151
|
+
@logger.info("http socket responding to request for configuration settings")
|
152
|
+
if @http_query_string and @http_query_string.downcase.include?("redacted=false")
|
153
|
+
send_response(200, "OK", @settings.to_hash)
|
154
|
+
else
|
155
|
+
send_response(200, "OK", redact_sensitive(@settings.to_hash))
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def http_request_errback(error)
|
160
|
+
@logger.error("http socket error while processing request", {
|
161
|
+
:error => error.to_s,
|
162
|
+
:backtrace => error.backtrace
|
163
|
+
})
|
164
|
+
@response = EM::DelegatedHttpResponse.new(self)
|
165
|
+
@response.content_type "application/json"
|
166
|
+
send_response(500, "Internal Server Error", {
|
167
|
+
"response" => "Internal Server Error: Check your Sensu logs for error details"
|
168
|
+
})
|
169
|
+
end
|
170
|
+
|
171
|
+
# This method is called to process HTTP requests
|
172
|
+
def process_http_request
|
173
|
+
@logger.debug("http socket processing", {
|
174
|
+
:http_request_method => @http_request_method,
|
175
|
+
:http_request_uri => @http_request_uri
|
176
|
+
})
|
177
|
+
@response = EM::DelegatedHttpResponse.new(self)
|
178
|
+
@response.content_type "application/json"
|
179
|
+
endpoint = @endpoints[@http_request_uri]
|
180
|
+
if endpoint
|
181
|
+
@logger.debug("http socket endpoint found", {
|
182
|
+
:http_request_uri => @http_request_uri,
|
183
|
+
:accepted_methods => endpoint["methods"].keys
|
184
|
+
})
|
185
|
+
method_name = @http_request_method.upcase
|
186
|
+
method_handler = endpoint["methods"][method_name]
|
187
|
+
if method_handler
|
188
|
+
@logger.debug("http socket executing handler", {
|
189
|
+
:method_name => method_name,
|
190
|
+
:http_request_uri => @http_request_uri
|
191
|
+
})
|
192
|
+
method_handler.call
|
193
|
+
else
|
194
|
+
@logger.debug("http socket method is not allowed for endpoint", {
|
195
|
+
:method_name => method_name,
|
196
|
+
:http_request_uri => @http_request_uri
|
197
|
+
})
|
198
|
+
send_response(405, "Method Not Allowed", {
|
199
|
+
:response => "Valid methods for this endpoint: #{endpoint['methods'].keys}"
|
200
|
+
})
|
201
|
+
end
|
202
|
+
else
|
203
|
+
@logger.warn("http socket unknown endpoint requested", :http_request_uri => @http_request_uri)
|
204
|
+
help_response = {
|
205
|
+
:endpoints => {}
|
206
|
+
}
|
207
|
+
@endpoints.each do |key, value|
|
208
|
+
help_response[:endpoints][key] ||= Hash.new
|
209
|
+
help_response[:endpoints][key]["help"] = value["help"]
|
210
|
+
help_response[:endpoints][key]["methods"] = value["methods"].keys
|
211
|
+
end
|
212
|
+
send_response(404, "Not Found", help_response)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|