sensu 0.21.0 → 0.22.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -0
- data/exe/sensu-install +44 -20
- data/lib/sensu/api/process.rb +1 -0
- data/lib/sensu/client/process.rb +4 -1
- data/lib/sensu/client/socket.rb +20 -10
- data/lib/sensu/constants.rb +1 -1
- data/lib/sensu/daemon.rb +1 -1
- data/lib/sensu/server/process.rb +124 -20
- data/sensu.gemspec +2 -1
- metadata +18 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cc83c24129d23bc39b4f06d48b7cdba8abf0f851
|
4
|
+
data.tar.gz: 6aca4babe99fb2e63e610204532322af7714f726
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dae436f84c219b052231da1d79be00a829f312c823020fa9e6c535555c1995ed69fec6c051da467ec3f7a8633333f1365b068e13f68da007d82f34e56a43af93
|
7
|
+
data.tar.gz: a164bcfc4ce2673eb3fafd41276d7074659893864f634de39a2832435f01469a4a3e49424ccd25ab5dc5d82b57aed7d86ffe3d5deb4d11404a310ece5c7c43f5
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,44 @@
|
|
1
|
+
## 0.22.0 - 2016-01-29
|
2
|
+
|
3
|
+
### Features
|
4
|
+
|
5
|
+
Client registration events are optionally created and processed (handled,
|
6
|
+
etc.) when a client is first added to the client registry. To enable this
|
7
|
+
functionality, configure a "registration" handler definition on Sensu
|
8
|
+
server(s), or define a client specific registration handler in the client
|
9
|
+
definition, e.g. `{"client": "registration": {"handler": "debug"}}`.
|
10
|
+
|
11
|
+
Client auto de-registration on sensu-client process stop is now supported
|
12
|
+
by the Sensu package init script. Setting `CLIENT_DEREGISTER_ON_STOP=true`
|
13
|
+
and `CLIENT_DEREGISTER_HANDLER=example` in `/etc/default/sensu` will cause
|
14
|
+
the Sensu client to publish a check result to trigger the event handler
|
15
|
+
named "example", before its process stops.
|
16
|
+
|
17
|
+
Added support for Sensu client signatures, used to sign client keepalive
|
18
|
+
and check result transport messages, for the purposes of source
|
19
|
+
(publisher) verification. The client definition attribute "signature" is
|
20
|
+
used to set the client signature, e.g. `"signature": "6zvyb8lm7fxcs7yw"`.
|
21
|
+
A client signature can only be set once, the client must be deleted from
|
22
|
+
the registry before its signature can be changed or removed. Client
|
23
|
+
keepalives and check results that are not signed with the correct
|
24
|
+
signature are logged (warn) and discarded. This feature is NOT a
|
25
|
+
replacement for existing and proven security measures.
|
26
|
+
|
27
|
+
The Sensu plugin installation tool, `sensu-install`, will no longer
|
28
|
+
install a plugin if a or specified version has already been installed.
|
29
|
+
|
30
|
+
The Sensu client socket now supports UTF-8 encoding.
|
31
|
+
|
1
32
|
## 0.21.0 - 2015-11-13
|
2
33
|
|
34
|
+
### Important
|
35
|
+
|
36
|
+
Using the Sensu embedded Ruby for Sensu checks, mutators, and handlers has
|
37
|
+
become a common practice. The Sensu 0.21 packages changed the default
|
38
|
+
value of `EMBEDDED_RUBY` from `false` to `true`, allowing Sensu plugins to
|
39
|
+
use the embedded Ruby by default. This change makes it easier to get
|
40
|
+
started with Sensu.
|
41
|
+
|
3
42
|
### Features
|
4
43
|
|
5
44
|
Added a Sensu plugin installation tool, `sensu-install`, making it easier
|
data/exe/sensu-install
CHANGED
@@ -36,36 +36,60 @@ module Sensu
|
|
36
36
|
puts "[SENSU-INSTALL] #{message}"
|
37
37
|
end
|
38
38
|
|
39
|
+
def plugin_gem_installed?(raw_gem, options={})
|
40
|
+
log "determining if Sensu plugin gem '#{raw_gem}' is already installed ..."
|
41
|
+
gem_name, gem_version = raw_gem.split(":")
|
42
|
+
gem_command = "gem list -i #{gem_name}"
|
43
|
+
gem_command << " --version '#{gem_version}'" if gem_version
|
44
|
+
log gem_command if options[:verbose]
|
45
|
+
if system(gem_command)
|
46
|
+
log "Sensu plugin gem '#{gem_name}' has already been installed"
|
47
|
+
true
|
48
|
+
else
|
49
|
+
log "Sensu plugin gem '#{gem_name}' has not been installed" if options[:verbose]
|
50
|
+
false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def install_plugin_gem(raw_gem, options={})
|
55
|
+
log "installing Sensu plugin gem '#{raw_gem}'"
|
56
|
+
gem_name, gem_version = raw_gem.split(":")
|
57
|
+
gem_command = "gem install #{gem_name}"
|
58
|
+
gem_command << " --version '#{gem_version}'" if gem_version
|
59
|
+
gem_command << " --no-ri --no-rdoc"
|
60
|
+
gem_command << " --verbose" if options[:verbose]
|
61
|
+
gem_command << " --source #{options[:source]}" if options[:source]
|
62
|
+
log gem_command if options[:verbose]
|
63
|
+
unless system(gem_command)
|
64
|
+
log "failed to install Sensu plugin gem '#{gem_name}'"
|
65
|
+
log "you can run the sensu-install command again with --verbose for more info" unless options[:verbose]
|
66
|
+
log "please take note of any failure messages above"
|
67
|
+
log "make sure you have build tools installed (e.g. gcc)"
|
68
|
+
log "trying to determine the Sensu plugin homepage for #{gem_name} ..."
|
69
|
+
system("gem specification #{gem_name} -r | grep homepage")
|
70
|
+
exit 2
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
39
74
|
def install_plugins(plugins, options={})
|
40
75
|
log "installing Sensu plugins ..."
|
41
76
|
log "provided Sensu plugins: #{plugins}" if options[:verbose]
|
42
|
-
|
77
|
+
plugin_gems = plugins.map do |plugin|
|
43
78
|
if plugin.start_with?("sensu-plugins-")
|
44
79
|
plugin
|
45
80
|
else
|
46
81
|
"sensu-plugins-#{plugin}"
|
47
82
|
end
|
48
83
|
end
|
49
|
-
log "Sensu plugin gems
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
gems
|
54
|
-
|
55
|
-
|
56
|
-
log gem_command if options[:verbose]
|
57
|
-
unless system(gem_command)
|
58
|
-
log "failed to install Sensu plugin gem: #{gem}"
|
59
|
-
log "you can run the sensu-install command again with --verbose for more info" unless options[:verbose]
|
60
|
-
log "please take note of any failure messages above"
|
61
|
-
log "make sure you have build tools installed (e.g. gcc)"
|
62
|
-
log "trying to determine Sensu plugin homepage for #{gem} ..."
|
63
|
-
clean_gem = gem.split(":").first
|
64
|
-
system("gem specification #{clean_gem} -r | grep homepage")
|
65
|
-
exit 2
|
66
|
-
end
|
84
|
+
log "compiled Sensu plugin gems: #{plugin_gems}" if options[:verbose]
|
85
|
+
plugin_gems.reject! do |raw_gem|
|
86
|
+
plugin_gem_installed?(raw_gem, options)
|
87
|
+
end
|
88
|
+
log "Sensu plugin gems to be installed: #{plugin_gems}"
|
89
|
+
plugin_gems.each do |raw_gem|
|
90
|
+
install_plugin_gem(raw_gem, options)
|
67
91
|
end
|
68
|
-
log "successfully
|
92
|
+
log "successfully installed Sensu plugins: #{plugins}"
|
69
93
|
end
|
70
94
|
|
71
95
|
def run
|
data/lib/sensu/api/process.rb
CHANGED
@@ -449,6 +449,7 @@ module Sensu
|
|
449
449
|
settings.logger.info("deleting client from registry", :client_name => client_name)
|
450
450
|
settings.redis.srem("clients", client_name) do
|
451
451
|
settings.redis.del("client:#{client_name}")
|
452
|
+
settings.redis.del("client:#{client_name}:signature")
|
452
453
|
settings.redis.del("events:#{client_name}")
|
453
454
|
settings.redis.smembers("result:#{client_name}") do |checks|
|
454
455
|
checks.each do |check_name|
|
data/lib/sensu/client/process.rb
CHANGED
@@ -77,7 +77,9 @@ module Sensu
|
|
77
77
|
# check result is composed of a client (name) and a check
|
78
78
|
# definition, containing check `:output` and `:status`. JSON
|
79
79
|
# serialization is used when publishing the check result payload
|
80
|
-
# to the transport pipe.
|
80
|
+
# to the transport pipe. The check result is signed with the
|
81
|
+
# client signature if configured, for source validation.
|
82
|
+
# Transport errors are logged.
|
81
83
|
#
|
82
84
|
# @param check [Hash]
|
83
85
|
def publish_check_result(check)
|
@@ -85,6 +87,7 @@ module Sensu
|
|
85
87
|
:client => @settings[:client][:name],
|
86
88
|
:check => check
|
87
89
|
}
|
90
|
+
payload[:signature] = @settings[:client][:signature] if @settings[:client][:signature]
|
88
91
|
@logger.info("publishing check result", :payload => payload)
|
89
92
|
@transport.publish(:direct, "results", MultiJson.dump(payload)) do |info|
|
90
93
|
if info[:error]
|
data/lib/sensu/client/socket.rb
CHANGED
@@ -141,9 +141,7 @@ module Sensu
|
|
141
141
|
:client => @settings[:client][:name],
|
142
142
|
:check => check.merge(:issued => Time.now.to_i)
|
143
143
|
}
|
144
|
-
@logger.info("publishing check result",
|
145
|
-
:payload => payload
|
146
|
-
})
|
144
|
+
@logger.info("publishing check result", :payload => payload)
|
147
145
|
@transport.publish(:direct, "results", MultiJson.dump(payload))
|
148
146
|
end
|
149
147
|
|
@@ -186,16 +184,14 @@ module Sensu
|
|
186
184
|
#
|
187
185
|
# @param [String] data to be processed.
|
188
186
|
def process_data(data)
|
189
|
-
if data.
|
190
|
-
@logger.warn("socket received non-ascii characters")
|
191
|
-
respond("invalid")
|
192
|
-
elsif data.strip == "ping"
|
187
|
+
if data.strip == "ping"
|
193
188
|
@logger.debug("socket received ping")
|
194
189
|
respond("pong")
|
195
190
|
else
|
196
|
-
@logger.debug("socket received data",
|
197
|
-
|
198
|
-
|
191
|
+
@logger.debug("socket received data", :data => data)
|
192
|
+
unless valid_utf8?(data)
|
193
|
+
@logger.warn("data from socket is not a valid UTF-8 sequence, processing it anyways", :data => data)
|
194
|
+
end
|
199
195
|
begin
|
200
196
|
parse_check_result(data)
|
201
197
|
rescue => error
|
@@ -208,6 +204,20 @@ module Sensu
|
|
208
204
|
end
|
209
205
|
end
|
210
206
|
|
207
|
+
# Tests if the argument (data) is a valid UTF-8 sequence.
|
208
|
+
#
|
209
|
+
# @param [String] data to be tested.
|
210
|
+
def valid_utf8?(data)
|
211
|
+
utf8_string_pattern = /\A([\x00-\x7f]|
|
212
|
+
[\xc2-\xdf][\x80-\xbf]|
|
213
|
+
\xe0[\xa0-\xbf][\x80-\xbf]|
|
214
|
+
[\xe1-\xef][\x80-\xbf]{2}|
|
215
|
+
\xf0[\x90-\xbf][\x80-\xbf]{2}|
|
216
|
+
[\xf1-\xf7][\x80-\xbf]{3})*\z/nx
|
217
|
+
data = data.force_encoding('BINARY') if data.respond_to?(:force_encoding)
|
218
|
+
return data =~ utf8_string_pattern
|
219
|
+
end
|
220
|
+
|
211
221
|
# This method is called whenever data is received. For UDP, it
|
212
222
|
# will only be called once, the original data length can be
|
213
223
|
# expected. For TCP, this method may be called several times, data
|
data/lib/sensu/constants.rb
CHANGED
data/lib/sensu/daemon.rb
CHANGED
data/lib/sensu/server/process.rb
CHANGED
@@ -37,19 +37,102 @@ module Sensu
|
|
37
37
|
@handling_event_count = 0
|
38
38
|
end
|
39
39
|
|
40
|
+
# Create a registration check definition for a client. Client
|
41
|
+
# definitions may contain `:registration` configuration,
|
42
|
+
# containing custom attributes and handler information. By
|
43
|
+
# default, the registration check definition sets the `:handler`
|
44
|
+
# to `registration`. If the client provides its own
|
45
|
+
# `:registration` configuration, it's deep merged with the
|
46
|
+
# defaults. The check `:name`, `:output`, `:status`, `:issued`,
|
47
|
+
# and `:executed` values are always overridden to guard against
|
48
|
+
# an invalid definition.
|
49
|
+
def create_registration_check(client)
|
50
|
+
check = {:handler => "registration"}
|
51
|
+
if client.has_key?(:registration)
|
52
|
+
check = deep_merge(check, client[:registration])
|
53
|
+
end
|
54
|
+
timestamp = Time.now.to_i
|
55
|
+
overrides = {
|
56
|
+
:name => "registration",
|
57
|
+
:output => "new client registration",
|
58
|
+
:status => 1,
|
59
|
+
:issued => timestamp,
|
60
|
+
:executed => timestamp
|
61
|
+
}
|
62
|
+
check.merge(overrides)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Create and process a client registration event. A registration
|
66
|
+
# event is created when a Sensu client is first added to the
|
67
|
+
# client registry. The `create_registration_check()` method is
|
68
|
+
# called to create a registration check definition for the
|
69
|
+
# client.
|
70
|
+
#
|
71
|
+
# @param client [Hash] definition.
|
72
|
+
def create_client_registration_event(client)
|
73
|
+
event = {
|
74
|
+
:id => random_uuid,
|
75
|
+
:client => client,
|
76
|
+
:check => create_registration_check(client),
|
77
|
+
:occurrences => 1,
|
78
|
+
:action => :create,
|
79
|
+
:timestamp => Time.now.to_i
|
80
|
+
}
|
81
|
+
process_event(event)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Process an initial client registration, when it is first added
|
85
|
+
# to the client registry. If a registration handler is defined
|
86
|
+
# or the client specifies one, a client registration event is
|
87
|
+
# created and processed (handled, etc.) for the client
|
88
|
+
# (`create_client_registration_event()`).
|
89
|
+
#
|
90
|
+
# @param client [Hash] definition.
|
91
|
+
def process_client_registration(client)
|
92
|
+
if @settings.handler_exists?("registration") || client[:registration]
|
93
|
+
create_client_registration_event(client)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
40
97
|
# Update the Sensu client registry, stored in Redis. Sensu
|
41
98
|
# client data is used to provide additional event context and
|
42
|
-
# enable agent health monitoring.
|
43
|
-
#
|
99
|
+
# enable agent health monitoring. The client registry supports
|
100
|
+
# client signatures, unique string identifiers used for
|
101
|
+
# keepalive and result source verification. If a client has a
|
102
|
+
# signature, all further registry updates for the client must
|
103
|
+
# have the same signature. A client can begin to use a signature
|
104
|
+
# if one was not previously configured. JSON serialization is
|
105
|
+
# used for the stored client data.
|
44
106
|
#
|
45
107
|
# @param client [Hash]
|
46
|
-
# @param callback [Proc] to call
|
47
|
-
#
|
108
|
+
# @param callback [Proc] to call with the success status
|
109
|
+
# (true/false) indicating if the client data has been added to
|
110
|
+
# (or updated) the registry or discarded due to client signature
|
111
|
+
# mismatch.
|
48
112
|
def update_client_registry(client, &callback)
|
49
113
|
@logger.debug("updating client registry", :client => client)
|
50
|
-
|
51
|
-
|
52
|
-
|
114
|
+
client_key = "client:#{client[:name]}"
|
115
|
+
signature_key = "#{client_key}:signature"
|
116
|
+
@redis.setnx(signature_key, client[:signature]) do |created|
|
117
|
+
process_client_registration(client) if created
|
118
|
+
@redis.get(signature_key) do |signature|
|
119
|
+
if signature.empty? && client[:signature]
|
120
|
+
@redis.set(signature_key, client[:signature])
|
121
|
+
end
|
122
|
+
if signature.empty? || (client[:signature] == signature)
|
123
|
+
@redis.set(client_key, MultiJson.dump(client)) do
|
124
|
+
@redis.sadd("clients", client[:name]) do
|
125
|
+
callback.call(true) if callback
|
126
|
+
end
|
127
|
+
end
|
128
|
+
else
|
129
|
+
@logger.warn("invalid client signature", {
|
130
|
+
:client => client,
|
131
|
+
:signature => signature
|
132
|
+
})
|
133
|
+
@logger.warn("not updating client in the registry", :client => client)
|
134
|
+
callback.call(false) if callback
|
135
|
+
end
|
53
136
|
end
|
54
137
|
end
|
55
138
|
end
|
@@ -381,13 +464,17 @@ module Sensu
|
|
381
464
|
:keepalives => false,
|
382
465
|
:version => VERSION
|
383
466
|
}
|
384
|
-
update_client_registry(client
|
467
|
+
update_client_registry(client) do
|
468
|
+
callback.call(client)
|
469
|
+
end
|
385
470
|
end
|
386
471
|
|
387
472
|
# Retrieve a client (data) from Redis if it exists. If a client
|
388
473
|
# does not already exist, create one (a blank) using the
|
389
474
|
# `client_key` as the client name. Dynamically create client
|
390
|
-
# data can be updated using the API (POST /clients/:client).
|
475
|
+
# data can be updated using the API (POST /clients/:client). If
|
476
|
+
# a client does exist and it has a client signature, the check
|
477
|
+
# result must have a matching signature or it is discarded.
|
391
478
|
#
|
392
479
|
# @param result [Hash] data.
|
393
480
|
# @param callback [Proc] to be called with client data, either
|
@@ -397,7 +484,19 @@ module Sensu
|
|
397
484
|
@redis.get("client:#{client_key}") do |client_json|
|
398
485
|
unless client_json.nil?
|
399
486
|
client = MultiJson.load(client_json)
|
400
|
-
|
487
|
+
if client[:signature]
|
488
|
+
if client[:signature] == result[:signature]
|
489
|
+
callback.call(client)
|
490
|
+
else
|
491
|
+
@logger.warn("invalid check result signature", {
|
492
|
+
:result => result,
|
493
|
+
:client => client
|
494
|
+
})
|
495
|
+
@logger.warn("not retrieving client from the registry", :result => result)
|
496
|
+
end
|
497
|
+
else
|
498
|
+
callback.call(client)
|
499
|
+
end
|
401
500
|
else
|
402
501
|
create_client(client_key, &callback)
|
403
502
|
end
|
@@ -572,9 +671,11 @@ module Sensu
|
|
572
671
|
|
573
672
|
# Publish a check result to the transport for processing. A
|
574
673
|
# check result is composed of a client name and a check
|
575
|
-
# definition, containing check `:output` and `:status`.
|
576
|
-
#
|
577
|
-
#
|
674
|
+
# definition, containing check `:output` and `:status`. A client
|
675
|
+
# signature is added to the check result payload if one is
|
676
|
+
# registered for the client. JSON serialization is used when
|
677
|
+
# publishing the check result payload to the transport pipe.
|
678
|
+
# Transport errors are logged.
|
578
679
|
#
|
579
680
|
# @param client_name [String]
|
580
681
|
# @param check [Hash]
|
@@ -583,13 +684,16 @@ module Sensu
|
|
583
684
|
:client => client_name,
|
584
685
|
:check => check
|
585
686
|
}
|
586
|
-
@
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
687
|
+
@redis.get("client:#{client_name}:signature") do |signature|
|
688
|
+
payload[:signature] = signature if signature
|
689
|
+
@logger.debug("publishing check result", :payload => payload)
|
690
|
+
@transport.publish(:direct, "results", MultiJson.dump(payload)) do |info|
|
691
|
+
if info[:error]
|
692
|
+
@logger.error("failed to publish check result", {
|
693
|
+
:payload => payload,
|
694
|
+
:error => info[:error].to_s
|
695
|
+
})
|
696
|
+
end
|
593
697
|
end
|
594
698
|
end
|
595
699
|
end
|
data/sensu.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.add_dependency "uuidtools", "2.1.5"
|
19
19
|
s.add_dependency "eventmachine", "1.0.8"
|
20
20
|
s.add_dependency "sensu-logger", "1.1.0"
|
21
|
-
s.add_dependency "sensu-settings", "3.
|
21
|
+
s.add_dependency "sensu-settings", "3.3.0"
|
22
22
|
s.add_dependency "sensu-extension", "1.3.0"
|
23
23
|
s.add_dependency "sensu-extensions", "1.4.0"
|
24
24
|
s.add_dependency "sensu-transport", "3.3.0"
|
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
|
|
31
31
|
s.add_development_dependency "rake", "~> 10.3"
|
32
32
|
s.add_development_dependency "rspec", "~> 3.0.0"
|
33
33
|
s.add_development_dependency "em-http-request", "~> 1.1"
|
34
|
+
s.add_development_dependency "addressable", "2.3.8"
|
34
35
|
|
35
36
|
s.files = Dir.glob("{exe,lib}/**/*") + %w[sensu.gemspec README.md CHANGELOG.md MIT-LICENSE.txt]
|
36
37
|
s.executables = s.files.grep(%r{^exe/}) { |file| File.basename(file) }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sensu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.22.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Porter
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2016-01-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: multi_json
|
@@ -73,14 +73,14 @@ dependencies:
|
|
73
73
|
requirements:
|
74
74
|
- - '='
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: 3.
|
76
|
+
version: 3.3.0
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
81
|
- - '='
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version: 3.
|
83
|
+
version: 3.3.0
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
85
|
name: sensu-extension
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -235,6 +235,20 @@ dependencies:
|
|
235
235
|
- - "~>"
|
236
236
|
- !ruby/object:Gem::Version
|
237
237
|
version: '1.1'
|
238
|
+
- !ruby/object:Gem::Dependency
|
239
|
+
name: addressable
|
240
|
+
requirement: !ruby/object:Gem::Requirement
|
241
|
+
requirements:
|
242
|
+
- - '='
|
243
|
+
- !ruby/object:Gem::Version
|
244
|
+
version: 2.3.8
|
245
|
+
type: :development
|
246
|
+
prerelease: false
|
247
|
+
version_requirements: !ruby/object:Gem::Requirement
|
248
|
+
requirements:
|
249
|
+
- - '='
|
250
|
+
- !ruby/object:Gem::Version
|
251
|
+
version: 2.3.8
|
238
252
|
description: A monitoring framework that aims to be simple, malleable, and scalable.
|
239
253
|
email:
|
240
254
|
- portertech@gmail.com
|