rollbar-notification-rules-generator 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.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: []
|