rollbar-notification-rules-generator 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +170 -0
- data/Rakefile +8 -0
- data/exe/rollbar-notification-rules-generator +34 -0
- data/lib/rollbar/notification/channel.rb +33 -0
- data/lib/rollbar/notification/condition/base.rb +41 -0
- data/lib/rollbar/notification/condition/environment.rb +27 -0
- data/lib/rollbar/notification/condition/framework.rb +22 -0
- data/lib/rollbar/notification/condition/level.rb +45 -0
- data/lib/rollbar/notification/condition/path.rb +60 -0
- data/lib/rollbar/notification/condition/rate.rb +48 -0
- data/lib/rollbar/notification/condition/title.rb +33 -0
- data/lib/rollbar/notification/rule.rb +145 -0
- data/lib/rollbar/notification/trigger.rb +125 -0
- data/lib/rollbar/notification.rb +33 -0
- data/rollbar-notification-rules-generator.gemspec +27 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7741d04c00243c59a3451ae2626619c1950012aae49833d0354e65a378312eb9
|
4
|
+
data.tar.gz: 2295ce6a7e025b9f51b9080bc3e39e87fbcdafdbe1798f5a7621a1078be85a96
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7b43b62215e964fbe5e3790ba57573c1a93ed56745ee1eb6adabc2add13cfde0a23decb78d0289e78fb92f97b30a47623839780cbe8ac330153f047bcb2d999d
|
7
|
+
data.tar.gz: d2ee76d26ae45c3be12dcd972cc7d69b6fcd8c72d6dd96a922b364b601be90fa958609d8971cf6056bafc23ceca8cdec51611ec24bf2255a0a262c5d623fd46e
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 abicky
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
# rollbar-notification-rules-generator
|
2
|
+
|
3
|
+
`rollbar-notification-rules-generator` is a CLI tool that generates Rollbar notification rules
|
4
|
+
that are mutually exclusive from a simple YAML file.
|
5
|
+
If you outputs the rules in the Terraform language, you can create the rules with Terraform.
|
6
|
+
|
7
|
+
For example, assume that you want to notify errors whose title contains the substring "foo"
|
8
|
+
to the Slack channel "alert-foo" every time they occur and other errors to the default channel.
|
9
|
+
You can write the Rollbar notification rules in the Terraform language as follows:
|
10
|
+
|
11
|
+
```terraform
|
12
|
+
resource "rollbar_notification" "slack_occurrence_0" {
|
13
|
+
channel = "slack"
|
14
|
+
|
15
|
+
rule {
|
16
|
+
trigger = "occurrence"
|
17
|
+
filters {
|
18
|
+
type = "level"
|
19
|
+
operation = "gte"
|
20
|
+
value = "error"
|
21
|
+
}
|
22
|
+
filters {
|
23
|
+
type = "title"
|
24
|
+
operation = "within"
|
25
|
+
value = "foo"
|
26
|
+
}
|
27
|
+
}
|
28
|
+
config {
|
29
|
+
channel = "alert-foo"
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
resource "rollbar_notification" "slack_occurrence_1" {
|
34
|
+
channel = "slack"
|
35
|
+
|
36
|
+
rule {
|
37
|
+
trigger = "occurrence"
|
38
|
+
filters {
|
39
|
+
type = "level"
|
40
|
+
operation = "gte"
|
41
|
+
value = "error"
|
42
|
+
}
|
43
|
+
filters {
|
44
|
+
type = "title"
|
45
|
+
operation = "nwithin"
|
46
|
+
value = "foo"
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
```
|
51
|
+
|
52
|
+
As you can see, you have to add the filter whose operation is "nwithin" to the second rule.
|
53
|
+
If there are more rules, you have to write complicated rules to make them mutually exclusive
|
54
|
+
and struggle to maintain them.
|
55
|
+
With this tool, you can write the rules in YAML format as follows:
|
56
|
+
|
57
|
+
```yaml
|
58
|
+
channel: "slack"
|
59
|
+
triggers:
|
60
|
+
occurrence:
|
61
|
+
- conditions:
|
62
|
+
- type: "level"
|
63
|
+
operation: "gte"
|
64
|
+
value: "error"
|
65
|
+
- type: "title"
|
66
|
+
operation: "within"
|
67
|
+
value: "foo"
|
68
|
+
configs:
|
69
|
+
- channel: "alert-foo"
|
70
|
+
- conditions:
|
71
|
+
- type: "level"
|
72
|
+
operation: "gte"
|
73
|
+
value: "error"
|
74
|
+
```
|
75
|
+
|
76
|
+
You don't have to add another condition to the second rule because the tool generates rules
|
77
|
+
to make Rollbar behave as if it tries to find the first rule from the top down that matches
|
78
|
+
all the conditions.
|
79
|
+
|
80
|
+
## Installation
|
81
|
+
|
82
|
+
$ gem install rollbar-notification-rules-generator
|
83
|
+
|
84
|
+
## Usage
|
85
|
+
|
86
|
+
```
|
87
|
+
Usage: rollbar-notification-rules-generator [options] <input>
|
88
|
+
--format FORMAT terraform|text (default: terraform)
|
89
|
+
```
|
90
|
+
|
91
|
+
The input file is in YAML format like the following:
|
92
|
+
|
93
|
+
```yaml
|
94
|
+
channel: "slack" # slack or pagerduty
|
95
|
+
# This optional value is used as provider meta-argument of Terraform.
|
96
|
+
terraform_provider: rollbar.alias_value
|
97
|
+
# You can define variables, and they are expanded with the syntax "${{ var.variable_name }}".
|
98
|
+
variables:
|
99
|
+
slack_channel_for_foo: "alert-foo"
|
100
|
+
triggers:
|
101
|
+
# You can specify the following triggers:
|
102
|
+
# * new_item
|
103
|
+
# * deploy
|
104
|
+
# * reactivated_item
|
105
|
+
# * reopened_item
|
106
|
+
# * occurrence_rate
|
107
|
+
# * exp_repeat_item
|
108
|
+
# * resolved_item
|
109
|
+
# * occurrence
|
110
|
+
occurrence:
|
111
|
+
# Each element corresponds to a Rollbar notification rule.
|
112
|
+
# For more details, the following documents help:
|
113
|
+
# * https://docs.rollbar.com/reference/post_api-1-notifications-slack-rules
|
114
|
+
# * https://docs.rollbar.com/reference/post_api-1-notifications-pagerduty
|
115
|
+
# * https://registry.terraform.io/providers/rollbar/rollbar/latest/docs/resources/notification
|
116
|
+
# Note that the tool hasn't supported some types yet.
|
117
|
+
- conditions:
|
118
|
+
- type: "level"
|
119
|
+
operation: "gte"
|
120
|
+
value: "error"
|
121
|
+
- type: "title"
|
122
|
+
operation: "within"
|
123
|
+
value: "foo"
|
124
|
+
# Each element corresponds to "config."
|
125
|
+
# If you specify multiple elements, as many rules as the elements are created,
|
126
|
+
# and Rollbar notifies items according to each configuration.
|
127
|
+
configs:
|
128
|
+
- channel: ${{ var.slack_channel_for_foo }}
|
129
|
+
- conditions:
|
130
|
+
- type: "level"
|
131
|
+
operation: "gte"
|
132
|
+
value: "error"
|
133
|
+
```
|
134
|
+
|
135
|
+
The text format makes it easier to read the generated rules as follows:
|
136
|
+
|
137
|
+
```
|
138
|
+
# Slack
|
139
|
+
## Every Occurrence
|
140
|
+
### Rule 0
|
141
|
+
conditions:
|
142
|
+
level >= error
|
143
|
+
title contains substring "foo"
|
144
|
+
|
145
|
+
config:
|
146
|
+
channel = "alert-foo"
|
147
|
+
|
148
|
+
### Rule 1
|
149
|
+
conditions:
|
150
|
+
level >= error
|
151
|
+
title does not contain substring "foo"
|
152
|
+
```
|
153
|
+
|
154
|
+
## Examples
|
155
|
+
|
156
|
+
See the YAML files in [spec/files/yaml](spec/files/yaml). Their output files are in [spec/files/tf](spec/files/tf) and [spec/files/txt](spec/files/txt).
|
157
|
+
|
158
|
+
## Development
|
159
|
+
|
160
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
161
|
+
|
162
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `rollbar-notification-rules-generator.gemspec`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
163
|
+
|
164
|
+
## Contributing
|
165
|
+
|
166
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/abicky/rollbar-notification-rules-generator.
|
167
|
+
|
168
|
+
## License
|
169
|
+
|
170
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "optparse"
|
3
|
+
|
4
|
+
require "rollbar/notification"
|
5
|
+
|
6
|
+
FORMATS = %w[terraform text]
|
7
|
+
|
8
|
+
opts = { format: "terraform" }
|
9
|
+
opt_parser = OptionParser.new do |opt|
|
10
|
+
opt.banner = "Usage: #{File.basename(__FILE__)} [options] <input>"
|
11
|
+
opt.on("--format FORMAT", FORMATS, "#{FORMATS.join("|")} (default: #{opts[:format]})")
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
opt_parser.parse!(into: opts)
|
16
|
+
rescue OptionParser::ParseError => e
|
17
|
+
$stderr.puts "Error: #{e.message}\n\n"
|
18
|
+
$stderr.puts opt_parser.help
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
|
22
|
+
input = ARGV[0]
|
23
|
+
unless input
|
24
|
+
$stderr.puts opt_parser.help
|
25
|
+
exit 1
|
26
|
+
end
|
27
|
+
|
28
|
+
notification = Rollbar::Notification.new(input)
|
29
|
+
case opts[:format]
|
30
|
+
when "terraform"
|
31
|
+
puts notification.to_tf
|
32
|
+
when "text"
|
33
|
+
puts notification.to_s
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "rollbar/notification/trigger"
|
3
|
+
|
4
|
+
module Rollbar
|
5
|
+
class Notification
|
6
|
+
class Channel
|
7
|
+
SUPPORTED_CHANNELS = %w[slack pagerduty]
|
8
|
+
|
9
|
+
# @param channel [String]
|
10
|
+
# @param triggers [Hash{String => Array<Hash>}]
|
11
|
+
# @param variables [Hash{String => String}]
|
12
|
+
def initialize(channel, triggers, variables)
|
13
|
+
unless SUPPORTED_CHANNELS.include?(channel)
|
14
|
+
raise ArgumentError, "Unsupported channel: #{channel}"
|
15
|
+
end
|
16
|
+
|
17
|
+
@triggers = triggers.map do |trigger, rules|
|
18
|
+
Rollbar::Notification::Trigger.new(channel, trigger, rules, variables)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [String]
|
23
|
+
def to_s
|
24
|
+
@triggers.map(&:to_s).join.chomp
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param provider [String]
|
28
|
+
def to_tf(provider)
|
29
|
+
@triggers.map { |t| t.to_tf(provider) }.join("\n")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rollbar
|
4
|
+
class Notification
|
5
|
+
module Condition
|
6
|
+
class Base
|
7
|
+
attr_reader :type, :operation, :value
|
8
|
+
|
9
|
+
# @param operation [String]
|
10
|
+
# @param value [String]
|
11
|
+
def initialize(operation, value)
|
12
|
+
unless self.class::SUPPORTED_OPERATIONS.include?(operation)
|
13
|
+
raise ArgumentError, "Unsupported operation: #{operation}"
|
14
|
+
end
|
15
|
+
|
16
|
+
@operation = operation
|
17
|
+
@value = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_tf
|
21
|
+
<<~TF
|
22
|
+
filters {
|
23
|
+
type = "#{@type}"
|
24
|
+
operation = "#{@operation}"
|
25
|
+
value = "#{@value}"
|
26
|
+
}
|
27
|
+
TF
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param other [Base]
|
31
|
+
# @return [Boolean]
|
32
|
+
def redundant_to?(other)
|
33
|
+
self.class == other.class &&
|
34
|
+
@operation == "neq" &&
|
35
|
+
other.operation == "eq" &&
|
36
|
+
@value != other.value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "rollbar/notification/condition/base"
|
3
|
+
|
4
|
+
module Rollbar
|
5
|
+
class Notification
|
6
|
+
module Condition
|
7
|
+
class Environment < Base
|
8
|
+
SUPPORTED_OPERATIONS = %w[eq neq]
|
9
|
+
|
10
|
+
def initialize(operation, value)
|
11
|
+
super
|
12
|
+
@type = "environment"
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [String]
|
16
|
+
def to_s
|
17
|
+
"#{@type} #{@operation == "eq" ? "==" : "!="} #{@value}"
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Environment]
|
21
|
+
def build_complement_condition
|
22
|
+
self.class.new(@operation == "eq" ? "neq" : "eq", @value)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "rollbar/notification/condition/base"
|
3
|
+
|
4
|
+
module Rollbar
|
5
|
+
class Notification
|
6
|
+
module Condition
|
7
|
+
class Framework < Base
|
8
|
+
SUPPORTED_OPERATIONS = %w[eq]
|
9
|
+
|
10
|
+
def initialize(operation, value)
|
11
|
+
super
|
12
|
+
@type = "framework"
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [String]
|
16
|
+
def to_s
|
17
|
+
"#{@type} #{@value}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "rollbar/notification/condition/base"
|
3
|
+
|
4
|
+
module Rollbar
|
5
|
+
class Notification
|
6
|
+
module Condition
|
7
|
+
class Level < Base
|
8
|
+
SUPPORTED_OPERATIONS = %w[eq gte]
|
9
|
+
SUPPORTED_VALUES = %w[debug info warning error critical]
|
10
|
+
|
11
|
+
# @param lowest_target_level [Integer]
|
12
|
+
# @return [Array<Level>]
|
13
|
+
def self.build_eq_conditions_from(lowest_target_level)
|
14
|
+
SUPPORTED_VALUES[lowest_target_level..].map do |value|
|
15
|
+
new("eq", value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :level
|
20
|
+
|
21
|
+
# @param operation [String]
|
22
|
+
# @param value [String]
|
23
|
+
def initialize(operation, value)
|
24
|
+
super
|
25
|
+
@type = "level"
|
26
|
+
|
27
|
+
@level = SUPPORTED_VALUES.index(value)
|
28
|
+
unless @level
|
29
|
+
raise ArgumentError, "Unsupported value: #{value}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [String]
|
34
|
+
def to_s
|
35
|
+
"#{@type} #{@operation == "eq" ? "==" : ">="} #{@value}"
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Array<String>]
|
39
|
+
def target_level_values
|
40
|
+
@operation == "eq" ? [@value] : SUPPORTED_VALUES[@level..]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "rollbar/notification/condition/base"
|
3
|
+
|
4
|
+
module Rollbar
|
5
|
+
class Notification
|
6
|
+
module Condition
|
7
|
+
class Path < Base
|
8
|
+
SUPPORTED_OPERATIONS = %w[eq neq within nwithin regex nregex exists nexists]
|
9
|
+
OPERATION_TO_TEXT = {
|
10
|
+
"eq" => "==",
|
11
|
+
"neq" => "!=",
|
12
|
+
"within" => "contains substring",
|
13
|
+
"nwithin" => "does not contain substring",
|
14
|
+
"regex" => "contains substring matching regex",
|
15
|
+
"nregex" => "does not contain substring matching regex",
|
16
|
+
"exists" => "exists",
|
17
|
+
"nexists" => "does not exist",
|
18
|
+
}
|
19
|
+
|
20
|
+
attr_reader :path
|
21
|
+
|
22
|
+
# @param path [String]
|
23
|
+
# @param operation [String]
|
24
|
+
# @param value [String]
|
25
|
+
def initialize(path, operation, value)
|
26
|
+
super(operation, value)
|
27
|
+
@type = "path"
|
28
|
+
@path = path
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_tf
|
32
|
+
<<~TF
|
33
|
+
filters {
|
34
|
+
type = "#{@type}"
|
35
|
+
path = "#{@path}"
|
36
|
+
operation = "#{@operation}"
|
37
|
+
value = "#{@value}"
|
38
|
+
}
|
39
|
+
TF
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [String]
|
43
|
+
def to_s
|
44
|
+
%Q{#{@type} #{@path} #{OPERATION_TO_TEXT[@operation]} "#{@value}"}
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Path]
|
48
|
+
def build_complement_condition
|
49
|
+
new_operation = @operation.start_with?("n") ? @operation.delete_prefix("n") : "n#{@operation}"
|
50
|
+
self.class.new(@path, new_operation, @value)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Boolean]
|
54
|
+
def redundant_to?(other)
|
55
|
+
super && @path == other.path
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rollbar
|
4
|
+
class Notification
|
5
|
+
module Condition
|
6
|
+
class Rate
|
7
|
+
PERIOD_TO_TEXT = {
|
8
|
+
60 => "1 minute",
|
9
|
+
300 => "5 minutes",
|
10
|
+
1800 => "30 minutes",
|
11
|
+
3600 => "1 hour",
|
12
|
+
86400 => "1 day",
|
13
|
+
}
|
14
|
+
|
15
|
+
attr_reader :type
|
16
|
+
|
17
|
+
# @param count [Integer]
|
18
|
+
# @param period [Integer]
|
19
|
+
def initialize(count, period)
|
20
|
+
@type = "rate"
|
21
|
+
@count = count
|
22
|
+
@period = period
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String]
|
26
|
+
def to_s
|
27
|
+
"At least #{@count} occurrences within #{PERIOD_TO_TEXT[@period]}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_tf
|
31
|
+
<<~TF
|
32
|
+
filters {
|
33
|
+
type = "#{@type}"
|
34
|
+
count = #{@count}
|
35
|
+
period = #{@period}
|
36
|
+
}
|
37
|
+
TF
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param other [Base]
|
41
|
+
# @return [Boolean]
|
42
|
+
def redundant_to?(other)
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "rollbar/notification/condition/base"
|
3
|
+
|
4
|
+
module Rollbar
|
5
|
+
class Notification
|
6
|
+
module Condition
|
7
|
+
class Title < Base
|
8
|
+
SUPPORTED_OPERATIONS = %w[within nwithin regex nregex]
|
9
|
+
OPERATION_TO_TEXT = {
|
10
|
+
"within" => "contains substring",
|
11
|
+
"nwithin" => "does not contain substring",
|
12
|
+
"regex" => "contains substring matching regex",
|
13
|
+
"nregex" => "does not contain substring matching regex",
|
14
|
+
}
|
15
|
+
|
16
|
+
def initialize(operation, value)
|
17
|
+
super
|
18
|
+
@type = "title"
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [String]
|
22
|
+
def to_s
|
23
|
+
%Q{#{@type} #{OPERATION_TO_TEXT[@operation]} "#{@value}"}
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_complement_condition
|
27
|
+
new_operation = @operation.start_with?("n") ? @operation.delete_prefix("n") : "n#{@operation}"
|
28
|
+
self.class.new(new_operation, @value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "rollbar/notification/condition/environment"
|
3
|
+
require "rollbar/notification/condition/framework"
|
4
|
+
require "rollbar/notification/condition/level"
|
5
|
+
require "rollbar/notification/condition/path"
|
6
|
+
require "rollbar/notification/condition/rate"
|
7
|
+
require "rollbar/notification/condition/title"
|
8
|
+
|
9
|
+
module Rollbar
|
10
|
+
class Notification
|
11
|
+
class Rule
|
12
|
+
attr_reader :conditions, :configs
|
13
|
+
|
14
|
+
# @param rule [Hash]
|
15
|
+
def initialize(rule)
|
16
|
+
@conditions = rule.fetch("conditions").map do |condition|
|
17
|
+
case condition.fetch("type")
|
18
|
+
when "environment"
|
19
|
+
Rollbar::Notification::Condition::Environment.new(condition.fetch("operation"), condition.fetch("value"))
|
20
|
+
when "framework"
|
21
|
+
Rollbar::Notification::Condition::Framework.new(condition.fetch("operation"), condition.fetch("value"))
|
22
|
+
when "level"
|
23
|
+
Rollbar::Notification::Condition::Level.new(condition.fetch("operation"), condition.fetch("value"))
|
24
|
+
when "path"
|
25
|
+
Rollbar::Notification::Condition::Path.new(condition.fetch("path"), condition.fetch("operation"), condition.fetch("value"))
|
26
|
+
when "rate"
|
27
|
+
Rollbar::Notification::Condition::Rate.new(condition.fetch("count"), condition.fetch("period"))
|
28
|
+
when "title"
|
29
|
+
Rollbar::Notification::Condition::Title.new(condition.fetch("operation"), condition.fetch("value"))
|
30
|
+
else
|
31
|
+
raise ArgumentError, "Unsupported condition type: #{condition.fetch("type")}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
@configs = rule.fetch("configs", [{}])
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize_dup(original)
|
38
|
+
@conditions = original.conditions.dup
|
39
|
+
remove_instance_variable(:@level_condition)
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Rule]
|
44
|
+
def remove_redundant_conditions!
|
45
|
+
@conditions.delete_if do |condition|
|
46
|
+
@conditions.any? { |other| condition.redundant_to?(other) }
|
47
|
+
end
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param old_condition [Condition::Base]
|
52
|
+
# @param new_condition [Condition::Base]
|
53
|
+
# @return [Rule]
|
54
|
+
def replace_condition!(old_condition, new_condition)
|
55
|
+
@conditions[@conditions.index(old_condition)] = new_condition
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Integer]
|
60
|
+
def lowest_target_level
|
61
|
+
level_condition&.level || 0
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [String]
|
65
|
+
def lowest_target_level_value
|
66
|
+
Rollbar::Notification::Condition::Level::SUPPORTED_VALUES[lowest_target_level]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Splits rules if necessary.
|
70
|
+
# Assuming the following two rules:
|
71
|
+
# level = critical, title contains substring "bar"
|
72
|
+
# level >= error, title contains substring "baz"
|
73
|
+
# the second rule will be split into two rules with "eq" operation:
|
74
|
+
# level = critical, title contains substring "bar"
|
75
|
+
# level = critical, title contains substring "baz"
|
76
|
+
# level = error, title contains substring "baz"
|
77
|
+
# whereas assuming the following two rules:
|
78
|
+
# level = warning, title contains substring "bar"
|
79
|
+
# level >= error, title contains substring "baz"
|
80
|
+
# this method doesn't split the second rule because each rule
|
81
|
+
# is already mutually exclusive.
|
82
|
+
#
|
83
|
+
# @param highest_lowest_target_level [Integer] the highest lowest_target_level
|
84
|
+
# among the preceding rules.
|
85
|
+
# @return [Array<Rule>]
|
86
|
+
def split_rules(highest_lowest_target_level)
|
87
|
+
return [dup] if level_condition&.operation == "eq" || highest_lowest_target_level <= lowest_target_level
|
88
|
+
|
89
|
+
new_level_conditions = Rollbar::Notification::Condition::Level.build_eq_conditions_from(lowest_target_level)
|
90
|
+
if level_condition
|
91
|
+
new_level_conditions.map do |condition|
|
92
|
+
dup.replace_condition!(level_condition, condition)
|
93
|
+
end
|
94
|
+
else
|
95
|
+
new_level_conditions.map do |condition|
|
96
|
+
dup.add_conditions!(condition)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# @param new_conditions [Condition::Base, Array<Condition::Base>]
|
102
|
+
# @return [Rule]
|
103
|
+
def add_conditions!(new_conditions)
|
104
|
+
Array(new_conditions).each do |new_condition|
|
105
|
+
@conditions << new_condition
|
106
|
+
end
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
# @return [Hash{String => Array<Condition::Base>}]
|
111
|
+
def build_additional_conditions_set_for_subsequent_rules
|
112
|
+
target_levels = level_condition&.target_level_values || Rollbar::Notification::Condition::Level::SUPPORTED_VALUES
|
113
|
+
|
114
|
+
conditions_with_complement = @conditions.select { |c| c.respond_to?(:build_complement_condition) }
|
115
|
+
return {} if conditions_with_complement.empty?
|
116
|
+
|
117
|
+
if conditions_with_complement.size == 1
|
118
|
+
additional_conditions = [[conditions_with_complement.first.build_complement_condition]]
|
119
|
+
else
|
120
|
+
# [cond1, cond2, cond3]
|
121
|
+
# => [
|
122
|
+
# [cond1, cond2, not-cond3],
|
123
|
+
# [cond1, not-cond2, cond3],
|
124
|
+
# [cond1, not-cond2, not-cond3],
|
125
|
+
# [not-cond1, cond2, cond3],
|
126
|
+
# [not-cond1, cond2, not-cond3],
|
127
|
+
# [not-cond1, not-cond2, cond3],
|
128
|
+
# [not-cond1, not-cond2, not-cond3],
|
129
|
+
# ]
|
130
|
+
additional_conditions = conditions_with_complement.map do |condition|
|
131
|
+
[condition, condition.build_complement_condition]
|
132
|
+
end.reduce(&:product).map(&:flatten) - [conditions_with_complement]
|
133
|
+
end
|
134
|
+
target_levels.zip([additional_conditions].cycle).to_h
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def level_condition
|
140
|
+
return @level_condition if defined?(@level_condition)
|
141
|
+
@level_condition = @conditions.find { |c| c.type == "level" }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "erb"
|
3
|
+
|
4
|
+
require "rollbar/notification/rule"
|
5
|
+
|
6
|
+
module Rollbar
|
7
|
+
class Notification
|
8
|
+
class Trigger
|
9
|
+
TRIGGER_TO_TEXT = {
|
10
|
+
"deploy" => "Deploy",
|
11
|
+
"exp_repeat_item" => "10^nth Occurrence",
|
12
|
+
"occurrence_rate" => "High Occurrence Rate",
|
13
|
+
"new_item" => "New Item",
|
14
|
+
"occurrence" => "Every Occurrence",
|
15
|
+
"reactivated_item" => "Item Reactivated",
|
16
|
+
"reopened_item" => "Item Reopened",
|
17
|
+
"resolved_item" => "Item Resolved",
|
18
|
+
}
|
19
|
+
|
20
|
+
TEXT_TEMPLATE = ERB.new(<<~TEXT)
|
21
|
+
conditions:
|
22
|
+
<%= conditions.map { |condition| condition.to_s.gsub(/^/, " ") }.join("\n").chomp %><% unless config.empty? %><% max_key_len = config.keys.map(&:size).max %>
|
23
|
+
|
24
|
+
config:
|
25
|
+
<%= config.compact.map { |key, value| ' %-*s = %s' % [max_key_len, key, value.inspect] }.join("\n") %><% end %>
|
26
|
+
TEXT
|
27
|
+
|
28
|
+
TF_TEMPLATE = ERB.new(<<~TF)
|
29
|
+
resource "rollbar_notification" "<%= resource_name %>" {<% if provider %>
|
30
|
+
provider = <%= provider %>
|
31
|
+
<% end %>
|
32
|
+
channel = "<%= channel %>"
|
33
|
+
|
34
|
+
rule {
|
35
|
+
trigger = "<%= trigger %>"
|
36
|
+
<%= conditions.map { |condition| condition.to_tf.gsub(/^/, " ") }.join.chomp %>
|
37
|
+
}<% unless config.empty? %><% max_key_len = config.keys.map(&:size).max %>
|
38
|
+
config {
|
39
|
+
<%= config.compact.map { |key, value| ' %-*s = %s' % [max_key_len, key, value.inspect] }.join("\n") %>
|
40
|
+
}<% end %>
|
41
|
+
}
|
42
|
+
TF
|
43
|
+
|
44
|
+
# @param channel [String]
|
45
|
+
# @param name [String]
|
46
|
+
# @param rules [Array<Hash>]
|
47
|
+
def initialize(channel, name, rules, variables)
|
48
|
+
@channel = channel
|
49
|
+
@name = name
|
50
|
+
@rules = rules.map do |rule|
|
51
|
+
Rollbar::Notification::Rule.new(rule)
|
52
|
+
end
|
53
|
+
@variables = variables
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
str = +"## #{TRIGGER_TO_TEXT.fetch(@name)}\n"
|
58
|
+
i = -1
|
59
|
+
build_mutually_exclusive_rules.each do |rule|
|
60
|
+
rule.configs.map do |config|
|
61
|
+
i += 1
|
62
|
+
str << "### Rule #{i}\n"
|
63
|
+
str << TEXT_TEMPLATE.result_with_hash({
|
64
|
+
conditions: rule.conditions,
|
65
|
+
config: config,
|
66
|
+
}).gsub(/\${{\s*var\.(\w+)\s*}}/) { @variables.fetch($1) }
|
67
|
+
end
|
68
|
+
str << "\n"
|
69
|
+
end
|
70
|
+
|
71
|
+
str
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_tf(provider)
|
75
|
+
i = -1
|
76
|
+
build_mutually_exclusive_rules.flat_map do |rule|
|
77
|
+
rule.configs.map do |config|
|
78
|
+
i += 1
|
79
|
+
TF_TEMPLATE.result_with_hash({
|
80
|
+
resource_name: "#{@channel}_#{@name}_#{i}",
|
81
|
+
provider: provider,
|
82
|
+
channel: @channel,
|
83
|
+
trigger: @name,
|
84
|
+
conditions: rule.conditions,
|
85
|
+
config: config,
|
86
|
+
}).gsub(/\${{\s*var\.(\w+)\s*}}/) { @variables.fetch($1) }
|
87
|
+
end
|
88
|
+
end.join("\n")
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# @return [Array<Rule>]
|
94
|
+
def build_mutually_exclusive_rules
|
95
|
+
new_rules = []
|
96
|
+
level_value_to_additional_conditions_set = Hash.new([])
|
97
|
+
highest_lowest_target_level = 0
|
98
|
+
@rules.each do |rule|
|
99
|
+
rule.split_rules(highest_lowest_target_level).each do |new_rule|
|
100
|
+
additional_conditions_set = level_value_to_additional_conditions_set[new_rule.lowest_target_level_value]
|
101
|
+
if additional_conditions_set.empty?
|
102
|
+
new_rules << new_rule
|
103
|
+
else
|
104
|
+
additional_conditions_set.each do |additional_conditions|
|
105
|
+
new_rules << new_rule.dup
|
106
|
+
.add_conditions!(additional_conditions)
|
107
|
+
.remove_redundant_conditions!
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
lowest_target_level = rule.lowest_target_level
|
113
|
+
if lowest_target_level > highest_lowest_target_level
|
114
|
+
highest_lowest_target_level = lowest_target_level
|
115
|
+
end
|
116
|
+
level_value_to_additional_conditions_set.merge!(rule.build_additional_conditions_set_for_subsequent_rules) do |_, v1, v2|
|
117
|
+
v1.product(v2).map(&:flatten)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
new_rules
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "yaml"
|
3
|
+
|
4
|
+
require "rollbar/notification/channel"
|
5
|
+
|
6
|
+
module Rollbar
|
7
|
+
class Notification
|
8
|
+
CHANNEL_TO_TEXT = {
|
9
|
+
"slack" => "Slack",
|
10
|
+
"pagerduty" => "PagerDuty",
|
11
|
+
}
|
12
|
+
|
13
|
+
# @param config_file [String]
|
14
|
+
def initialize(config_file)
|
15
|
+
@config = YAML.load_file(config_file)
|
16
|
+
@channel = Rollbar::Notification::Channel.new(
|
17
|
+
@config.fetch("channel"),
|
18
|
+
@config.fetch("triggers"),
|
19
|
+
@config.fetch("variables", {})
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String]
|
24
|
+
def to_s
|
25
|
+
"# #{CHANNEL_TO_TEXT.fetch(@config.fetch("channel"))}\n#{@channel.to_s}"
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [String]
|
29
|
+
def to_tf
|
30
|
+
@channel.to_tf(@config["terraform_provider"])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "rollbar-notification-rules-generator"
|
5
|
+
spec.version = "0.2.0"
|
6
|
+
spec.authors = ["abicky"]
|
7
|
+
spec.email = ["takeshi.arabiki@gmail.com"]
|
8
|
+
|
9
|
+
spec.summary = "A CLI tool that generates Rollbar notification rules that are mutually exclusive from a simple YAML file"
|
10
|
+
spec.description = "This tool generates Rollbar notification rules that are mutually exclusive from a simple YAML file."
|
11
|
+
|
12
|
+
spec.homepage = "https://github.com/abicky/rollbar-notification-rules-generator"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.required_ruby_version = ">= 2.6.0"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/abicky/rollbar-notification-rules-generator"
|
18
|
+
|
19
|
+
spec.files = Dir.chdir(__dir__) do
|
20
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
21
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
22
|
+
end
|
23
|
+
end
|
24
|
+
spec.bindir = "exe"
|
25
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rollbar-notification-rules-generator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- abicky
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-12-14 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: This tool generates Rollbar notification rules that are mutually exclusive
|
14
|
+
from a simple YAML file.
|
15
|
+
email:
|
16
|
+
- takeshi.arabiki@gmail.com
|
17
|
+
executables:
|
18
|
+
- rollbar-notification-rules-generator
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- ".rspec"
|
23
|
+
- Gemfile
|
24
|
+
- LICENSE.txt
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- exe/rollbar-notification-rules-generator
|
28
|
+
- lib/rollbar/notification.rb
|
29
|
+
- lib/rollbar/notification/channel.rb
|
30
|
+
- lib/rollbar/notification/condition/base.rb
|
31
|
+
- lib/rollbar/notification/condition/environment.rb
|
32
|
+
- lib/rollbar/notification/condition/framework.rb
|
33
|
+
- lib/rollbar/notification/condition/level.rb
|
34
|
+
- lib/rollbar/notification/condition/path.rb
|
35
|
+
- lib/rollbar/notification/condition/rate.rb
|
36
|
+
- lib/rollbar/notification/condition/title.rb
|
37
|
+
- lib/rollbar/notification/rule.rb
|
38
|
+
- lib/rollbar/notification/trigger.rb
|
39
|
+
- rollbar-notification-rules-generator.gemspec
|
40
|
+
homepage: https://github.com/abicky/rollbar-notification-rules-generator
|
41
|
+
licenses:
|
42
|
+
- MIT
|
43
|
+
metadata:
|
44
|
+
homepage_uri: https://github.com/abicky/rollbar-notification-rules-generator
|
45
|
+
source_code_uri: https://github.com/abicky/rollbar-notification-rules-generator
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.6.0
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubygems_version: 3.3.26
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: A CLI tool that generates Rollbar notification rules that are mutually exclusive
|
65
|
+
from a simple YAML file
|
66
|
+
test_files: []
|