sensu-settings 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +20 -0
- data/README.md +28 -0
- data/Rakefile +6 -0
- data/lib/sensu/settings.rb +15 -0
- data/lib/sensu/settings/constants.rb +5 -0
- data/lib/sensu/settings/loader.rb +255 -0
- data/lib/sensu/settings/rules.rb +154 -0
- data/lib/sensu/settings/validator.rb +76 -0
- data/lib/sensu/settings/validators.rb +23 -0
- data/lib/sensu/settings/validators/api.rb +36 -0
- data/lib/sensu/settings/validators/check.rb +78 -0
- data/lib/sensu/settings/validators/client.rb +113 -0
- data/lib/sensu/settings/validators/filter.rb +18 -0
- data/lib/sensu/settings/validators/handler.rb +133 -0
- data/lib/sensu/settings/validators/mutator.rb +18 -0
- data/lib/sensu/settings/validators/subdue.rb +68 -0
- data/lib/sensu/settings/validators/transport.rb +20 -0
- data/sensu-settings.gemspec +24 -0
- data/spec/assets/conf.d/merger.json +11 -0
- data/spec/assets/conf.d/nested/file.json +11 -0
- data/spec/assets/config.json +126 -0
- data/spec/assets/invalid.json +2 -0
- data/spec/helpers.rb +8 -0
- data/spec/loader_spec.rb +171 -0
- data/spec/rules_spec.rb +65 -0
- data/spec/settings_spec.rb +13 -0
- data/spec/validator_spec.rb +619 -0
- metadata +153 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
module Sensu
|
2
|
+
module Settings
|
3
|
+
module Validators
|
4
|
+
module Mutator
|
5
|
+
# Validate a Sensu mutator definition.
|
6
|
+
# Validates: command, timeout
|
7
|
+
#
|
8
|
+
# @param mutator [Hash] sensu mutator definition.
|
9
|
+
def validate_mutator(mutator)
|
10
|
+
must_be_a_string(mutator[:command]) ||
|
11
|
+
invalid(mutator, "mutator command must be a string")
|
12
|
+
must_be_a_numeric_if_set(mutator[:timeout]) ||
|
13
|
+
invalid(mutator, "mutator timeout must be numeric")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Sensu
|
2
|
+
module Settings
|
3
|
+
module Validators
|
4
|
+
module Subdue
|
5
|
+
# Validate subdue time.
|
6
|
+
# Validates: begin, end
|
7
|
+
#
|
8
|
+
# @param scope [String] definition scope to report under.
|
9
|
+
# @param definition [Hash] sensu definition.
|
10
|
+
# @param object [Hash] to have begin and end validated.
|
11
|
+
def validate_subdue_time(scope, definition, object)
|
12
|
+
if is_a_hash?(object)
|
13
|
+
if either_are_set?(object[:begin], object[:end])
|
14
|
+
must_be_time(object[:begin], object[:end]) ||
|
15
|
+
invalid(definition, "#{scope} begin and end times must be valid")
|
16
|
+
end
|
17
|
+
else
|
18
|
+
invalid(definition, "#{scope} must be a hash")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Validate subdue days.
|
23
|
+
# Validates: days
|
24
|
+
#
|
25
|
+
# @param definition [Hash] sensu definition.
|
26
|
+
def validate_subdue_days(definition)
|
27
|
+
subdue = definition[:subdue]
|
28
|
+
must_be_an_array_if_set(subdue[:days]) ||
|
29
|
+
invalid(definition, "subdue days must be an array")
|
30
|
+
if is_an_array?(subdue[:days])
|
31
|
+
days = %w[sunday monday tuesday wednesday thursday friday saturday]
|
32
|
+
must_be_either(days, subdue[:days]) ||
|
33
|
+
invalid(definition, "subdue days must be valid days of the week")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Validate subdue exceptions.
|
38
|
+
# Validates: exceptions (begin, end)
|
39
|
+
#
|
40
|
+
# @param definition [Hash] sensu definition.
|
41
|
+
def validate_subdue_exceptions(definition)
|
42
|
+
subdue = definition[:subdue]
|
43
|
+
must_be_an_array_if_set(subdue[:exceptions]) ||
|
44
|
+
invalid(definition, "subdue exceptions must be an array")
|
45
|
+
if is_an_array?(subdue[:exceptions])
|
46
|
+
subdue[:exceptions].each do |exception|
|
47
|
+
validate_subdue_time("subdue exceptions", definition, exception)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Validate Sensu subdue, for either a check or handler definition.
|
53
|
+
#
|
54
|
+
# @param definition [Hash] sensu definition.
|
55
|
+
def validate_subdue(definition)
|
56
|
+
subdue = definition[:subdue]
|
57
|
+
validate_subdue_time("subdue", definition, subdue)
|
58
|
+
if is_a_hash?(subdue)
|
59
|
+
must_be_either_if_set(%w[handler publisher], subdue[:at]) ||
|
60
|
+
invalid(definition, "subdue at must be either handler or publisher")
|
61
|
+
validate_subdue_days(definition)
|
62
|
+
validate_subdue_exceptions(definition)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Sensu
|
2
|
+
module Settings
|
3
|
+
module Validators
|
4
|
+
module Transport
|
5
|
+
# Validate a Sensu transport definition.
|
6
|
+
# Validates: name
|
7
|
+
#
|
8
|
+
# @param transport [Hash] sensu transport definition.
|
9
|
+
def validate_transport(transport)
|
10
|
+
must_be_a_hash_if_set(transport) ||
|
11
|
+
invalid(transport, "transport must be a hash")
|
12
|
+
if is_a_hash?(transport)
|
13
|
+
must_be_a_string_if_set(transport[:name]) ||
|
14
|
+
invalid(transport, "transport name must be a string")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "sensu-settings"
|
5
|
+
spec.version = "0.0.1"
|
6
|
+
spec.authors = ["Sean Porter"]
|
7
|
+
spec.email = ["portertech@gmail.com"]
|
8
|
+
spec.summary = "The Sensu settings library, loader and validator"
|
9
|
+
spec.description = "The Sensu settings library, loader and validator"
|
10
|
+
spec.homepage = "https://github.com/sensu/sensu-settings"
|
11
|
+
spec.license = "MIT"
|
12
|
+
|
13
|
+
spec.files = `git ls-files -z`.split("\x0")
|
14
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
15
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
16
|
+
spec.require_paths = ["lib"]
|
17
|
+
|
18
|
+
spec.add_dependency "multi_json", "1.10.0"
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
21
|
+
spec.add_development_dependency "rake"
|
22
|
+
spec.add_development_dependency "rspec"
|
23
|
+
spec.add_development_dependency "codeclimate-test-reporter"
|
24
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
{
|
2
|
+
"api": {
|
3
|
+
"host": "localhost",
|
4
|
+
"port": 4567,
|
5
|
+
"user": "foo",
|
6
|
+
"password": "bar"
|
7
|
+
},
|
8
|
+
"filters": {
|
9
|
+
"production": {
|
10
|
+
"attributes": {
|
11
|
+
"client": {
|
12
|
+
"environment": "production"
|
13
|
+
}
|
14
|
+
}
|
15
|
+
},
|
16
|
+
"development": {
|
17
|
+
"negate": true,
|
18
|
+
"attributes": {
|
19
|
+
"client": {
|
20
|
+
"environment": "production"
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
24
|
+
},
|
25
|
+
"mutators": {
|
26
|
+
"noop": {
|
27
|
+
"command": "cat"
|
28
|
+
}
|
29
|
+
},
|
30
|
+
"handlers": {
|
31
|
+
"default": {
|
32
|
+
"type": "set",
|
33
|
+
"handlers": [
|
34
|
+
"debug"
|
35
|
+
]
|
36
|
+
},
|
37
|
+
"file": {
|
38
|
+
"type": "pipe",
|
39
|
+
"command": "cat > /tmp/sensu_event"
|
40
|
+
},
|
41
|
+
"filtered": {
|
42
|
+
"type": "pipe",
|
43
|
+
"command": "cat > /tmp/sensu_event",
|
44
|
+
"filter": "development"
|
45
|
+
},
|
46
|
+
"severities": {
|
47
|
+
"type": "pipe",
|
48
|
+
"command": "cat > /tmp/sensu_event",
|
49
|
+
"severities": [
|
50
|
+
"critical",
|
51
|
+
"unknown"
|
52
|
+
]
|
53
|
+
},
|
54
|
+
"flapping": {
|
55
|
+
"type": "pipe",
|
56
|
+
"command": "cat > /tmp/sensu_event",
|
57
|
+
"handle_flapping": true
|
58
|
+
},
|
59
|
+
"tcp": {
|
60
|
+
"type": "tcp",
|
61
|
+
"socket": {
|
62
|
+
"host": "localhost",
|
63
|
+
"port": 1234
|
64
|
+
}
|
65
|
+
},
|
66
|
+
"udp": {
|
67
|
+
"type": "udp",
|
68
|
+
"socket": {
|
69
|
+
"host": "localhost",
|
70
|
+
"port": 1234
|
71
|
+
}
|
72
|
+
},
|
73
|
+
"transport": {
|
74
|
+
"type": "transport",
|
75
|
+
"pipe": {
|
76
|
+
"type": "direct",
|
77
|
+
"name": "events"
|
78
|
+
}
|
79
|
+
}
|
80
|
+
},
|
81
|
+
"checks": {
|
82
|
+
"tokens": {
|
83
|
+
"command": "echo -n :::name::: :::nested.attribute::: && exit 2",
|
84
|
+
"subscribers": [
|
85
|
+
"test"
|
86
|
+
],
|
87
|
+
"interval": 1
|
88
|
+
},
|
89
|
+
"standalone": {
|
90
|
+
"command": "echo -n foobar && exit 1",
|
91
|
+
"standalone": true,
|
92
|
+
"interval": 1
|
93
|
+
},
|
94
|
+
"merger": {
|
95
|
+
"command": "this will be overwritten",
|
96
|
+
"subscribers": [
|
97
|
+
"foo"
|
98
|
+
]
|
99
|
+
},
|
100
|
+
"unpublished": {
|
101
|
+
"command": "/bin/true",
|
102
|
+
"publish": false,
|
103
|
+
"subscribers":[
|
104
|
+
"test"
|
105
|
+
]
|
106
|
+
}
|
107
|
+
},
|
108
|
+
"client": {
|
109
|
+
"name": "i-424242",
|
110
|
+
"address": "127.0.0.1",
|
111
|
+
"subscriptions": [
|
112
|
+
"test"
|
113
|
+
],
|
114
|
+
"nested": {
|
115
|
+
"attribute": true
|
116
|
+
},
|
117
|
+
"service": {
|
118
|
+
"password": "secret"
|
119
|
+
},
|
120
|
+
"keepalive": {
|
121
|
+
"thresholds": {
|
122
|
+
"warning": 10
|
123
|
+
}
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
data/spec/helpers.rb
ADDED
data/spec/loader_spec.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "helpers")
|
2
|
+
require "sensu/settings/loader"
|
3
|
+
|
4
|
+
describe "Sensu::Settings::Loader" do
|
5
|
+
include Helpers
|
6
|
+
|
7
|
+
before do
|
8
|
+
@loader = Sensu::Settings::Loader.new
|
9
|
+
@assets_dir = File.join(File.dirname(__FILE__), "assets")
|
10
|
+
@config_file = File.join(@assets_dir, "config.json")
|
11
|
+
@config_dir = File.join(@assets_dir, "conf.d")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "can provide a loader API" do
|
15
|
+
@loader.should respond_to(:load, :validate!)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "can provide indifferent access to settings" do
|
19
|
+
@loader[:checks].should be_kind_of(Hash)
|
20
|
+
@loader["checks"].should be_kind_of(Hash)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "can validate loaded settings" do
|
24
|
+
failures = @loader.validate!
|
25
|
+
failures.size.should eq(0)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "can load RabbitMQ settings from the environment" do
|
29
|
+
ENV["RABBITMQ_URL"] = "amqp://guest:guest@localhost:5672/"
|
30
|
+
@loader.load_env
|
31
|
+
@loader.warnings.size.should eq(1)
|
32
|
+
ENV["RABBITMQ_URL"] = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
it "can load Redis settings from the environment" do
|
36
|
+
ENV["REDIS_URL"] = "redis://username:password@localhost:6789"
|
37
|
+
@loader.load_env
|
38
|
+
@loader.warnings.size.should eq(1)
|
39
|
+
ENV["REDIS_URL"] = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it "can load Sensu API settings from the environment" do
|
43
|
+
ENV["API_PORT"] = "4567"
|
44
|
+
@loader.load_env
|
45
|
+
@loader.warnings.size.should eq(1)
|
46
|
+
ENV["API_PORT"] = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
it "can load Redis and Sensu API settings from the environment using alternative variables" do
|
50
|
+
ENV["REDISTOGO_URL"] = "redis://username:password@localhost:6789"
|
51
|
+
ENV["PORT"] = "4567"
|
52
|
+
@loader.load_env
|
53
|
+
@loader.warnings.size.should eq(2)
|
54
|
+
ENV["REDISTOGO_URL"] = nil
|
55
|
+
ENV["PORT"] = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
it "can load settings from a file" do
|
59
|
+
@loader.load_file(@config_file)
|
60
|
+
@loader.warnings.size.should eq(1)
|
61
|
+
warning = @loader.warnings.first
|
62
|
+
warning[:object].should eq(File.expand_path(@config_file))
|
63
|
+
warning[:message].should eq("loading config file")
|
64
|
+
@loader[:api][:port].should eq(4567)
|
65
|
+
@loader["api"]["port"].should eq(4567)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "can load settings from a file and validate them" do
|
69
|
+
@loader.load_file(@config_file)
|
70
|
+
failures = @loader.validate!
|
71
|
+
reasons = failures.map do |failure|
|
72
|
+
failure[:message]
|
73
|
+
end
|
74
|
+
reasons.should include("check interval must be an integer")
|
75
|
+
end
|
76
|
+
|
77
|
+
it "can attempt to load settings from a nonexistent file" do
|
78
|
+
@loader.load_file("/tmp/bananaphone")
|
79
|
+
warnings = @loader.warnings
|
80
|
+
warnings.size.should eq(2)
|
81
|
+
messages = warnings.map do |warning|
|
82
|
+
warning[:message]
|
83
|
+
end
|
84
|
+
messages.should include("config file does not exist or is not readable")
|
85
|
+
messages.should include("ignoring config file")
|
86
|
+
end
|
87
|
+
|
88
|
+
it "can attempt to load settings from a file with invalid JSON" do
|
89
|
+
@loader.load_file(File.join(@assets_dir, "invalid.json"))
|
90
|
+
warnings = @loader.warnings
|
91
|
+
warnings.size.should eq(3)
|
92
|
+
messages = warnings.map do |warning|
|
93
|
+
warning[:message]
|
94
|
+
end
|
95
|
+
messages.should include("loading config file")
|
96
|
+
messages.should include("config file must be valid json")
|
97
|
+
messages.should include("ignoring config file")
|
98
|
+
end
|
99
|
+
|
100
|
+
it "can load settings from files in a directory" do
|
101
|
+
@loader.load_directory(@config_dir)
|
102
|
+
warnings = @loader.warnings
|
103
|
+
warnings.size.should eq(4)
|
104
|
+
messages = warnings.map do |warning|
|
105
|
+
warning[:message]
|
106
|
+
end
|
107
|
+
messages.should include("loading config files from directory")
|
108
|
+
messages.should include("loading config file")
|
109
|
+
messages.should include("config file applied changes")
|
110
|
+
@loader[:checks][:nested][:command].should eq("true")
|
111
|
+
end
|
112
|
+
|
113
|
+
it "can attempt to load settings from files in a nonexistent directory" do
|
114
|
+
@loader.load_directory("/tmp/rottentomatos")
|
115
|
+
@loader.warnings.size.should eq(1)
|
116
|
+
warning = @loader.warnings.first
|
117
|
+
warning[:message].should eq("loading config files from directory")
|
118
|
+
end
|
119
|
+
|
120
|
+
it "can load settings from the environment, a file, and a directory" do
|
121
|
+
ENV["RABBITMQ_URL"] = "amqp://guest:guest@localhost:5672/"
|
122
|
+
settings = @loader.load(:config_file => @config_file, :config_dir => @config_dir)
|
123
|
+
settings[:rabbitmq].should eq(ENV["RABBITMQ_URL"])
|
124
|
+
settings[:api][:port].should eq(4567)
|
125
|
+
settings[:checks][:merger][:command].should eq("echo -n merger")
|
126
|
+
settings[:checks][:merger][:subscribers].should eq(["foo", "bar"])
|
127
|
+
settings[:checks][:nested][:command].should eq("true")
|
128
|
+
ENV["SENSU_CONFIG_FILES"].split(":").should eq(@loader.loaded_files)
|
129
|
+
ENV["RABBITMQ_URL"] = nil
|
130
|
+
end
|
131
|
+
|
132
|
+
it "can load settings and determine if certain definitions exist" do
|
133
|
+
@loader.load(:config_file => @config_file, :config_dir => @config_dir)
|
134
|
+
@loader.check_exists?("nonexistent").should be_false
|
135
|
+
@loader.check_exists?("tokens").should be_true
|
136
|
+
@loader.filter_exists?("nonexistent").should be_false
|
137
|
+
@loader.filter_exists?("development").should be_true
|
138
|
+
@loader.mutator_exists?("nonexistent").should be_false
|
139
|
+
@loader.mutator_exists?("noop").should be_true
|
140
|
+
@loader.handler_exists?("nonexistent").should be_false
|
141
|
+
@loader.handler_exists?("default").should be_true
|
142
|
+
end
|
143
|
+
|
144
|
+
it "can load settings and provide setting category accessors" do
|
145
|
+
@loader.load(:config_file => @config_file, :config_dir => @config_dir)
|
146
|
+
@loader.checks.should be_kind_of(Array)
|
147
|
+
@loader.checks.should_not be_empty
|
148
|
+
check = @loader.checks.detect do |check|
|
149
|
+
check[:name] == "tokens"
|
150
|
+
end
|
151
|
+
check[:interval].should eq(1)
|
152
|
+
@loader.filters.should be_kind_of(Array)
|
153
|
+
@loader.filters.should_not be_empty
|
154
|
+
filter = @loader.filters.detect do |filter|
|
155
|
+
filter[:name] == "development"
|
156
|
+
end
|
157
|
+
filter[:negate].should be_true
|
158
|
+
@loader.mutators.should be_kind_of(Array)
|
159
|
+
@loader.mutators.should_not be_empty
|
160
|
+
mutator = @loader.mutators.detect do |mutator|
|
161
|
+
mutator[:name] == "noop"
|
162
|
+
end
|
163
|
+
mutator[:command].should eq("cat")
|
164
|
+
@loader.handlers.should be_kind_of(Array)
|
165
|
+
@loader.handlers.should_not be_empty
|
166
|
+
handler = @loader.handlers.detect do |handler|
|
167
|
+
handler[:name] == "default"
|
168
|
+
end
|
169
|
+
handler[:type].should eq("set")
|
170
|
+
end
|
171
|
+
end
|