portertech-sensu-settings 10.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +28 -0
- data/lib/sensu/settings/constants.rb +5 -0
- data/lib/sensu/settings/loader.rb +502 -0
- data/lib/sensu/settings/rules.rb +166 -0
- data/lib/sensu/settings/validator.rb +88 -0
- data/lib/sensu/settings/validators/api.rb +65 -0
- data/lib/sensu/settings/validators/check.rb +253 -0
- data/lib/sensu/settings/validators/client.rb +238 -0
- data/lib/sensu/settings/validators/extension.rb +18 -0
- data/lib/sensu/settings/validators/filter.rb +27 -0
- data/lib/sensu/settings/validators/handler.rb +132 -0
- data/lib/sensu/settings/validators/mutator.rb +18 -0
- data/lib/sensu/settings/validators/sensu.rb +102 -0
- data/lib/sensu/settings/validators/tessen.rb +22 -0
- data/lib/sensu/settings/validators/time_window.rb +74 -0
- data/lib/sensu/settings/validators/transport.rb +22 -0
- data/lib/sensu/settings/validators.rb +29 -0
- data/lib/sensu/settings.rb +52 -0
- data/sensu-settings.gemspec +24 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4153f6c86a09ed8235ac34d06f7d869ddab639ed09db776dff89f764fe99377b
|
4
|
+
data.tar.gz: 32db3e0c760c611f630895d2eadc7e90665d6190e050c84ca56560286b6cd055
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e512898250001641dcda1b26bdcb740939c6e900804b134e0713fba0ef47cda55f85b3e21cfdd6173edeafa472e16bf2dffacf68bbce65483be5074959c38086
|
7
|
+
data.tar.gz: b23dc848b0b8884192f45c13d08e692eb49182c0034a12f0d59263426bf1258cf02d09d994e1c79f01692a578d69587c04f1a9275d1d3d03a2b806b3c68856f5
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2014 Heavy Water Operations, LLC.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Sensu::Settings
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/sensu/sensu-settings.svg?branch=master)](https://travis-ci.org/sensu/sensu-settings)
|
4
|
+
![Gem Version](https://img.shields.io/gem/v/sensu-settings.svg)
|
5
|
+
![MIT Licensed](https://img.shields.io/github/license/sensu/sensu.svg)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'sensu-settings'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
Documentation can be found [here](http://rubydoc.info/github/sensu/sensu-settings/Sensu/Settings).
|
20
|
+
|
21
|
+
## Contributing
|
22
|
+
|
23
|
+
0. By contributing to this project you agree to abide by the [code of conduct](https://sensuapp.org/conduct).
|
24
|
+
1. [Fork it](https://github.com/sensu/sensu-settings/fork)
|
25
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
26
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
27
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
28
|
+
5. Create a new Pull Request
|
@@ -0,0 +1,502 @@
|
|
1
|
+
require "sensu/settings/validator"
|
2
|
+
require "sensu/json"
|
3
|
+
require "tmpdir"
|
4
|
+
require "socket"
|
5
|
+
require "digest"
|
6
|
+
|
7
|
+
module Sensu
|
8
|
+
module Settings
|
9
|
+
class Loader
|
10
|
+
class Error < RuntimeError; end
|
11
|
+
|
12
|
+
# @!attribute [r] warnings
|
13
|
+
# @return [Array] loader warnings.
|
14
|
+
attr_reader :warnings
|
15
|
+
|
16
|
+
# @!attribute [r] errors
|
17
|
+
# @return [Array] loader errors.
|
18
|
+
attr_reader :errors
|
19
|
+
|
20
|
+
# @!attribute [r] loaded_files
|
21
|
+
# @return [Array] loaded config files.
|
22
|
+
attr_reader :loaded_files
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@warnings = []
|
26
|
+
@errors = []
|
27
|
+
@settings = default_settings
|
28
|
+
@indifferent_access = false
|
29
|
+
@loaded_files = []
|
30
|
+
self.class.create_category_methods
|
31
|
+
end
|
32
|
+
|
33
|
+
# Auto-detected defaults for client definition
|
34
|
+
#
|
35
|
+
# Client name defaults to system hostname.
|
36
|
+
# Client address defaults to first detected non-loopback ipv4 address.
|
37
|
+
#
|
38
|
+
# Client subscriptions are intentionally omitted here as sensu-client
|
39
|
+
# will provide defaults using client name after final settings are
|
40
|
+
# loaded.
|
41
|
+
#
|
42
|
+
# @return [Hash] default client settings
|
43
|
+
def client_defaults
|
44
|
+
{
|
45
|
+
:name => system_hostname,
|
46
|
+
:address => system_address
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
# Default settings.
|
51
|
+
#
|
52
|
+
# @return [Hash] settings.
|
53
|
+
def default_settings
|
54
|
+
default = {
|
55
|
+
:client => {},
|
56
|
+
:sensu => {
|
57
|
+
:spawn => {
|
58
|
+
:limit => 12
|
59
|
+
},
|
60
|
+
:keepalives => {
|
61
|
+
:thresholds => {
|
62
|
+
:warning => 120,
|
63
|
+
:critical => 180
|
64
|
+
}
|
65
|
+
}
|
66
|
+
},
|
67
|
+
:transport => {
|
68
|
+
:name => "rabbitmq",
|
69
|
+
:reconnect_on_error => true
|
70
|
+
}
|
71
|
+
}
|
72
|
+
CATEGORIES.each do |category|
|
73
|
+
default[category] = {}
|
74
|
+
end
|
75
|
+
if ["client", "rspec"].include?(sensu_service_name)
|
76
|
+
default[:client] = client_defaults
|
77
|
+
end
|
78
|
+
default
|
79
|
+
end
|
80
|
+
|
81
|
+
# Create setting category accessors and methods to test the
|
82
|
+
# existence of definitions. Called in initialize().
|
83
|
+
def self.create_category_methods
|
84
|
+
CATEGORIES.each do |category|
|
85
|
+
define_method(category) do
|
86
|
+
setting_category(category)
|
87
|
+
end
|
88
|
+
method_name = category.to_s.chop + "_exists?"
|
89
|
+
define_method(method_name.to_sym) do |name|
|
90
|
+
definition_exists?(category, name)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Access settings as an indifferent hash.
|
96
|
+
#
|
97
|
+
# @return [Hash] settings.
|
98
|
+
def to_hash
|
99
|
+
unless @indifferent_access
|
100
|
+
indifferent_access!
|
101
|
+
@hexdigest = nil
|
102
|
+
end
|
103
|
+
@settings
|
104
|
+
end
|
105
|
+
|
106
|
+
# Retrieve the setting object corresponding to a key, acting
|
107
|
+
# like a Hash object.
|
108
|
+
#
|
109
|
+
# @param key [String, Symbol]
|
110
|
+
# @return [Object] value for key.
|
111
|
+
def [](key)
|
112
|
+
to_hash[key]
|
113
|
+
end
|
114
|
+
|
115
|
+
# Create a SHA256 hex digest for the settings Hash object. The
|
116
|
+
# client definition scope is ignored when the current process is
|
117
|
+
# not a Sensu client, as it is essentially ignored and it will
|
118
|
+
# likely cause a sum mismatch between two Sensu service systems.
|
119
|
+
# This method will not recalculate the hex digest, unless the
|
120
|
+
# settings have been altered, determine by the values of
|
121
|
+
# `@hexdigest` and `@indifferent_access`.
|
122
|
+
#
|
123
|
+
# @return [String] SHA256 hex digest.
|
124
|
+
def hexdigest
|
125
|
+
if @hexdigest && @indifferent_access
|
126
|
+
@hexdigest
|
127
|
+
else
|
128
|
+
hash = case sensu_service_name
|
129
|
+
when "client", "rspec"
|
130
|
+
to_hash
|
131
|
+
else
|
132
|
+
to_hash.reject do |key, value|
|
133
|
+
key.to_s == "client"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
@hexdigest = Digest::SHA256.hexdigest(hash.to_s)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Load settings from the environment.
|
141
|
+
#
|
142
|
+
# Loads: SENSU_TRANSPORT_NAME, RABBITMQ_URL, REDIS_URL,
|
143
|
+
# SENSU_CLIENT_NAME, SENSU_CLIENT_ADDRESS
|
144
|
+
# SENSU_CLIENT_SUBSCRIPTIONS, SENSU_API_PORT
|
145
|
+
def load_env
|
146
|
+
load_transport_env
|
147
|
+
load_rabbitmq_env
|
148
|
+
load_redis_env
|
149
|
+
load_client_env
|
150
|
+
load_api_env
|
151
|
+
end
|
152
|
+
|
153
|
+
# Load settings from a JSON file.
|
154
|
+
#
|
155
|
+
# @param [String] file path.
|
156
|
+
# @param must_exist [TrueClass, FalseClass] if the file must
|
157
|
+
# exist and is readable.
|
158
|
+
def load_file(file, must_exist=true)
|
159
|
+
if File.file?(file) && File.readable?(file)
|
160
|
+
begin
|
161
|
+
warning("loading config file", :file => file)
|
162
|
+
contents = read_config_file(file)
|
163
|
+
config = contents.empty? ? {} : Sensu::JSON.load(contents)
|
164
|
+
merged = deep_merge(@settings, config)
|
165
|
+
unless @loaded_files.empty?
|
166
|
+
changes = deep_diff(@settings, merged)
|
167
|
+
warning("config file applied changes", {
|
168
|
+
:file => file,
|
169
|
+
:changes => changes
|
170
|
+
})
|
171
|
+
end
|
172
|
+
@settings = merged
|
173
|
+
@indifferent_access = false
|
174
|
+
@loaded_files << file
|
175
|
+
rescue Sensu::JSON::ParseError => error
|
176
|
+
load_error("config file must be valid json", {
|
177
|
+
:file => file,
|
178
|
+
:error => error.to_s
|
179
|
+
})
|
180
|
+
end
|
181
|
+
elsif must_exist
|
182
|
+
load_error("config file does not exist or is not readable", :file => file)
|
183
|
+
else
|
184
|
+
warning("config file does not exist or is not readable", :file => file)
|
185
|
+
warning("ignoring config file", :file => file)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Load settings from files in a directory. Files may be in
|
190
|
+
# nested directories.
|
191
|
+
#
|
192
|
+
# @param [String] directory path.
|
193
|
+
def load_directory(directory)
|
194
|
+
warning("loading config files from directory", :directory => directory)
|
195
|
+
path = directory.gsub(/\\(?=\S)/, "/")
|
196
|
+
if File.readable?(path) && File.executable?(path)
|
197
|
+
Dir.glob(File.join(path, "**{,/*/**}/*.json")).uniq.each do |file|
|
198
|
+
load_file(file)
|
199
|
+
end
|
200
|
+
else
|
201
|
+
load_error("insufficient permissions for loading", :directory => directory)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Load Sensu client settings overrides. This method adds any overrides to
|
206
|
+
# the client definition. Overrides include:
|
207
|
+
#
|
208
|
+
# * Ensuring client subscriptions include a single subscription based on the
|
209
|
+
# client name, e.g "client:i-424242".
|
210
|
+
def load_client_overrides
|
211
|
+
@settings[:client][:subscriptions] ||= []
|
212
|
+
if @settings[:client][:subscriptions].is_a?(Array)
|
213
|
+
@settings[:client][:subscriptions] << "client:#{@settings[:client][:name]}"
|
214
|
+
@settings[:client][:subscriptions].uniq!
|
215
|
+
warning("applied sensu client overrides", :client => @settings[:client])
|
216
|
+
@indifferent_access = false
|
217
|
+
else
|
218
|
+
warning("unable to apply sensu client overrides", {
|
219
|
+
:reason => "client subscriptions is not an array",
|
220
|
+
:client => @settings[:client]
|
221
|
+
})
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Load overrides, i.e. settings which should always be present.
|
226
|
+
# Examples include client settings overrides which ensure a per-client subscription.
|
227
|
+
def load_overrides!
|
228
|
+
load_client_overrides if ["client", "rspec"].include?(sensu_service_name)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Set Sensu settings related environment variables. This method
|
232
|
+
# sets `SENSU_LOADED_TEMPFILE` to a new temporary file path,
|
233
|
+
# a file containing the colon delimited list of loaded
|
234
|
+
# configuration files (using `create_loaded_tempfile!()`. The
|
235
|
+
# environment variable `SENSU_CONFIG_FILES` has been removed,
|
236
|
+
# due to the exec ARG_MAX (E2BIG) error when spawning processes
|
237
|
+
# after loading many configuration files (e.g. > 2000).
|
238
|
+
def set_env!
|
239
|
+
ENV["SENSU_LOADED_TEMPFILE"] = create_loaded_tempfile!
|
240
|
+
end
|
241
|
+
|
242
|
+
# Validate the loaded settings.
|
243
|
+
#
|
244
|
+
# @return [Array] validation failures.
|
245
|
+
def validate
|
246
|
+
validator = Validator.new
|
247
|
+
@errors += validator.run(@settings, sensu_service_name)
|
248
|
+
end
|
249
|
+
|
250
|
+
private
|
251
|
+
|
252
|
+
# Retrieve setting category definitions.
|
253
|
+
#
|
254
|
+
# @param [Symbol] category to retrive.
|
255
|
+
# @return [Array<Hash>] category definitions.
|
256
|
+
def setting_category(category)
|
257
|
+
@settings[category].map do |name, details|
|
258
|
+
details.merge(:name => name.to_s)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# Check to see if a definition exists in a category.
|
263
|
+
#
|
264
|
+
# @param [Symbol] category to inspect for the definition.
|
265
|
+
# @param [String] name of definition.
|
266
|
+
# @return [TrueClass, FalseClass]
|
267
|
+
def definition_exists?(category, name)
|
268
|
+
@settings[category].has_key?(name.to_sym)
|
269
|
+
end
|
270
|
+
|
271
|
+
# Creates an indifferent hash.
|
272
|
+
#
|
273
|
+
# @return [Hash] indifferent hash.
|
274
|
+
def indifferent_hash
|
275
|
+
Hash.new do |hash, key|
|
276
|
+
if key.is_a?(String)
|
277
|
+
hash[key.to_sym]
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Create a copy of a hash with indifferent access.
|
283
|
+
#
|
284
|
+
# @param hash [Hash] hash to make indifferent.
|
285
|
+
# @return [Hash] indifferent version of hash.
|
286
|
+
def with_indifferent_access(hash)
|
287
|
+
hash = indifferent_hash.merge(hash)
|
288
|
+
hash.each do |key, value|
|
289
|
+
if value.is_a?(Hash)
|
290
|
+
hash[key] = with_indifferent_access(value)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Update settings to have indifferent access.
|
296
|
+
def indifferent_access!
|
297
|
+
@settings = with_indifferent_access(@settings)
|
298
|
+
@indifferent_access = true
|
299
|
+
end
|
300
|
+
|
301
|
+
# Load Sensu transport settings from the environment. This
|
302
|
+
# method sets the Sensu transport name to `SENSU_TRANSPORT_NAME`
|
303
|
+
# if set.
|
304
|
+
def load_transport_env
|
305
|
+
if ENV["SENSU_TRANSPORT_NAME"]
|
306
|
+
@settings[:transport][:name] = ENV["SENSU_TRANSPORT_NAME"]
|
307
|
+
warning("using sensu transport name environment variable", :transport => @settings[:transport])
|
308
|
+
@indifferent_access = false
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
# Load Sensu RabbitMQ settings from the environment. This method
|
313
|
+
# sets the RabbitMQ settings to `RABBITMQ_URL` if set. The Sensu
|
314
|
+
# RabbitMQ transport accepts a URL string for options.
|
315
|
+
def load_rabbitmq_env
|
316
|
+
if ENV["RABBITMQ_URL"]
|
317
|
+
@settings[:rabbitmq] = ENV["RABBITMQ_URL"]
|
318
|
+
warning("using rabbitmq url environment variable", :rabbitmq => @settings[:rabbitmq])
|
319
|
+
@indifferent_access = false
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# Load Sensu Redis settings from the environment.
|
324
|
+
#
|
325
|
+
# This method evaluates the REDIS_SENTINEL_URLS and REDIS_URL environment variables
|
326
|
+
# and configures the Redis settings accordingly.
|
327
|
+
#
|
328
|
+
# When REDIS_SENTINEL_URLS is provided as a list of one or more
|
329
|
+
# comma-separated URLs, e.g.
|
330
|
+
# "redis://10.0.0.1:26379,redis://10.0.0.2:26379" these URLs will take
|
331
|
+
# precedence over the value provided by REDIS_URL, if any.
|
332
|
+
#
|
333
|
+
# As the redis library accepts a URL string for options. This
|
334
|
+
# configuration applies to data storage and the redis transport, if used.
|
335
|
+
def load_redis_env
|
336
|
+
if ENV["REDIS_SENTINEL_URLS"]
|
337
|
+
@settings[:redis] = {:sentinels => ENV["REDIS_SENTINEL_URLS"]}
|
338
|
+
warning("using redis sentinel url environment variable", :sentinels => @settings[:redis][:sentinels])
|
339
|
+
@indifferent_access = false
|
340
|
+
elsif ENV["REDIS_URL"]
|
341
|
+
@settings[:redis] = ENV["REDIS_URL"]
|
342
|
+
warning("using redis url environment variable", :redis => @settings[:redis])
|
343
|
+
@indifferent_access = false
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
# Load Sensu client settings from the environment. This method
|
348
|
+
# loads client settings from several variables:
|
349
|
+
# `SENSU_CLIENT_NAME`, `SENSU_CLIENT_ADDRESS`, and
|
350
|
+
# `SENSU_CLIENT_SUBSCRIPTIONS`.
|
351
|
+
def load_client_env
|
352
|
+
@settings[:client][:name] = ENV["SENSU_CLIENT_NAME"] if ENV["SENSU_CLIENT_NAME"]
|
353
|
+
@settings[:client][:address] = ENV["SENSU_CLIENT_ADDRESS"] if ENV["SENSU_CLIENT_ADDRESS"]
|
354
|
+
@settings[:client][:subscriptions] = ENV["SENSU_CLIENT_SUBSCRIPTIONS"].split(",") if ENV["SENSU_CLIENT_SUBSCRIPTIONS"]
|
355
|
+
if ENV.keys.any? {|k| k =~ /^SENSU_CLIENT/}
|
356
|
+
warning("using sensu client environment variables", :client => @settings[:client])
|
357
|
+
end
|
358
|
+
@indifferent_access = false
|
359
|
+
end
|
360
|
+
|
361
|
+
# Load Sensu API settings from the environment. This method sets
|
362
|
+
# the API port to `SENSU_API_PORT` if set.
|
363
|
+
def load_api_env
|
364
|
+
if ENV["SENSU_API_PORT"]
|
365
|
+
@settings[:api] ||= {}
|
366
|
+
@settings[:api][:port] = ENV["SENSU_API_PORT"].to_i
|
367
|
+
warning("using api port environment variable", :api => @settings[:api])
|
368
|
+
@indifferent_access = false
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
# Read a configuration file and force its encoding to 8-bit
|
373
|
+
# ASCII, ignoring invalid characters. If there is a UTF-8 BOM,
|
374
|
+
# it will be removed. Some JSON parsers force ASCII but do not
|
375
|
+
# remove the UTF-8 BOM if present, causing encoding conversion
|
376
|
+
# errors. This method is for consistency across Sensu::JSON
|
377
|
+
# adapters and system platforms.
|
378
|
+
#
|
379
|
+
# @param [String] file path to read.
|
380
|
+
# @return [String] file contents.
|
381
|
+
def read_config_file(file)
|
382
|
+
contents = IO.read(file)
|
383
|
+
if contents.respond_to?(:force_encoding)
|
384
|
+
encoding = ::Encoding::ASCII_8BIT
|
385
|
+
contents = contents.force_encoding(encoding)
|
386
|
+
contents.sub!("\xEF\xBB\xBF".force_encoding(encoding), "")
|
387
|
+
else
|
388
|
+
contents.sub!(/^\357\273\277/, "")
|
389
|
+
end
|
390
|
+
contents.strip
|
391
|
+
end
|
392
|
+
|
393
|
+
# Deep merge two hashes.
|
394
|
+
#
|
395
|
+
# @param [Hash] hash_one to serve as base.
|
396
|
+
# @param [Hash] hash_two to merge in.
|
397
|
+
# @return [Hash] deep merged hash.
|
398
|
+
def deep_merge(hash_one, hash_two)
|
399
|
+
merged = hash_one.dup
|
400
|
+
hash_two.each do |key, value|
|
401
|
+
merged[key] = case
|
402
|
+
when hash_one[key].is_a?(Hash) && value.is_a?(Hash)
|
403
|
+
deep_merge(hash_one[key], value)
|
404
|
+
when hash_one[key].is_a?(Array) && value.is_a?(Array)
|
405
|
+
hash_one[key].concat(value).uniq
|
406
|
+
else
|
407
|
+
value
|
408
|
+
end
|
409
|
+
end
|
410
|
+
merged
|
411
|
+
end
|
412
|
+
|
413
|
+
# Compare two hashes.
|
414
|
+
#
|
415
|
+
# @param [Hash] hash_one to compare.
|
416
|
+
# @param [Hash] hash_two to compare.
|
417
|
+
# @return [Hash] comparison diff hash.
|
418
|
+
def deep_diff(hash_one, hash_two)
|
419
|
+
keys = hash_one.keys.concat(hash_two.keys).uniq
|
420
|
+
keys.inject(Hash.new) do |diff, key|
|
421
|
+
unless hash_one[key] == hash_two[key]
|
422
|
+
if hash_one[key].is_a?(Hash) && hash_two[key].is_a?(Hash)
|
423
|
+
diff[key] = deep_diff(hash_one[key], hash_two[key])
|
424
|
+
else
|
425
|
+
diff[key] = [hash_one[key], hash_two[key]]
|
426
|
+
end
|
427
|
+
end
|
428
|
+
diff
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
# Create a temporary file containing the colon delimited list of
|
433
|
+
# loaded configuration files. Ruby TempFile is not used to
|
434
|
+
# create the temporary file as it would be removed if the Sensu
|
435
|
+
# service daemonizes (fork/detach). The file is created in the
|
436
|
+
# system temporary file directory for the platform (Linux,
|
437
|
+
# Windows, etc.) and the file name contains the Sensu service
|
438
|
+
# name to reduce the likelihood of one Sensu service affecting
|
439
|
+
# another.
|
440
|
+
#
|
441
|
+
# @return [String] tempfile path.
|
442
|
+
def create_loaded_tempfile!
|
443
|
+
dir = ENV["SENSU_LOADED_TEMPFILE_DIR"] || Dir.tmpdir
|
444
|
+
file_name = "sensu_#{sensu_service_name}_loaded_files"
|
445
|
+
path = File.join(dir, file_name)
|
446
|
+
File.open(path, "w") do |file|
|
447
|
+
file.write(@loaded_files.join(":"))
|
448
|
+
end
|
449
|
+
path
|
450
|
+
end
|
451
|
+
|
452
|
+
# Retrieve Sensu service name.
|
453
|
+
#
|
454
|
+
# @return [String] service name.
|
455
|
+
def sensu_service_name
|
456
|
+
File.basename($0).split("-").last
|
457
|
+
end
|
458
|
+
|
459
|
+
# Retrieve the system hostname. If the hostname cannot be
|
460
|
+
# determined and an error is thrown, return "unknown", the same
|
461
|
+
# value Sensu uses for JIT clients.
|
462
|
+
#
|
463
|
+
# @return [String] system hostname.
|
464
|
+
def system_hostname
|
465
|
+
Socket.gethostname rescue "unknown"
|
466
|
+
end
|
467
|
+
|
468
|
+
# Retrieve the system IP address. If a valid non-loopback
|
469
|
+
# IPv4 address cannot be found and an error is thrown,
|
470
|
+
# "unknown" will be returned.
|
471
|
+
#
|
472
|
+
# @return [String] system ip address
|
473
|
+
def system_address
|
474
|
+
Socket.ip_address_list.find { |address|
|
475
|
+
address.ipv4? && !address.ipv4_loopback?
|
476
|
+
}.ip_address rescue "unknown"
|
477
|
+
end
|
478
|
+
|
479
|
+
# Record a warning.
|
480
|
+
#
|
481
|
+
# @param message [String] warning message.
|
482
|
+
# @param data [Hash] warning context.
|
483
|
+
# @return [Array] current warnings.
|
484
|
+
def warning(message, data={})
|
485
|
+
@warnings << {
|
486
|
+
:message => message
|
487
|
+
}.merge(data)
|
488
|
+
end
|
489
|
+
|
490
|
+
# Record a load error and raise a load error exception.
|
491
|
+
#
|
492
|
+
# @param message [String] load error message.
|
493
|
+
# @param data [Hash] load error context.
|
494
|
+
def load_error(message, data={})
|
495
|
+
@errors << {
|
496
|
+
:message => message
|
497
|
+
}.merge(data)
|
498
|
+
raise(Error, message)
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
502
|
+
end
|