Skip to content

Commit 41f75e6

Browse files
committed
Initial commit
The server seems to work, and can pass along simple notifications All major features are unimplemented as of yet
0 parents  commit 41f75e6

18 files changed

+404
-0
lines changed

.gitignore

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/.bundle/
2+
/.yardoc
3+
/_yardoc/
4+
/coverage/
5+
/doc/
6+
/pkg/
7+
/spec/reports/
8+
/tmp/
9+
/vendor/
10+
Gemfile.lock
11+
config.yml

.gitlab-ci.yml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
image: "ruby:2.4"
3+
4+
# Cache gems in between builds
5+
cache:
6+
paths:
7+
- vendor/ruby
8+
9+
before_script:
10+
- gem install bundler --no-ri --no-rdoc
11+
- bundle install -j $(nproc) --path vendor
12+
13+
rubocop:
14+
script:
15+
- bundle exec rubocop
16+
17+
# rspec:
18+
# script:
19+
# - rspec spec

.rubocop.yml

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
AllCops:
3+
TargetRubyVersion: 2.4
4+
Exclude:
5+
- '*.spec'
6+
- 'Rakefile'
7+
- 'vendor/**/*'
8+
9+
# Don't enforce documentation
10+
Style/Documentation:
11+
Enabled: false
12+
13+
Style/FrozenStringLiteralComment:
14+
Enabled: false
15+
16+
Style/MultilineBlockChain:
17+
Enabled: false
18+
19+
Layout/IndentHeredoc:
20+
Enabled: false
21+
22+
Metrics/PerceivedComplexity:
23+
Enabled: false
24+
25+
Metrics/CyclomaticComplexity:
26+
Enabled: false
27+
28+
Style/RescueModifier:
29+
Enabled: false
30+
31+
Metrics/MethodLength:
32+
Max: 40
33+
34+
Metrics/LineLength:
35+
Max: 190
36+
37+
Metrics/AbcSize:
38+
Enabled: false
39+
40+
Performance/FixedSize:
41+
Exclude:
42+
- 'test/**/*'
43+
44+
Metrics/BlockLength:
45+
Exclude:
46+
- 'test/**/*'
47+
48+
Metrics/ClassLength:
49+
Max: 200
50+
Exclude:
51+
- 'test/**/*'
52+
53+
Lint/AmbiguousBlockAssociation:
54+
Enabled: false
55+
56+
Style/ClassAndModuleChildren:
57+
Exclude:
58+
- 'test/**/*'
59+
- 'app/controllers/concerns/foreman/**/*'

Gemfile

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
source "https://rubygems.org"
2+
3+
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4+
5+
# Specify your gem's dependencies in grafana_matrix.gemspec
6+
gemspec

LICENSE.txt

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2018 Alexander Olofsson
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.

README.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Grafana Matrix
2+
3+
A Grafana webhook ingress for sending Matrix notifications
4+
5+
## Installation
6+
7+
TODO
8+
9+
## Usage
10+
11+
TODO
12+
13+
## Contributing
14+
15+
Bug reports and pull requests are welcome on GitHub at https://github.com/ananace/grafana_matrix
16+
17+
## License
18+
19+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).

Rakefile

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
require "bundler/gem_tasks"
2+
require "rake/testtask"
3+
4+
Rake::TestTask.new(:test) do |t|
5+
t.libs << "test"
6+
t.libs << "lib"
7+
t.test_files = FileList["test/**/*_test.rb"]
8+
end
9+
10+
task :default => :test

bin/server

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env ruby
2+
3+
require 'grafana_matrix'
4+
require 'json'
5+
require 'matrix_sdk'
6+
require 'sinatra'
7+
8+
config = GrafanaMatrix::Config.new 'config.yml'
9+
renderer = GrafanaMatrix::Renderer.new
10+
11+
puts 'Loaded configuration;'
12+
puts config.inspect
13+
14+
post '/hook' do
15+
rule = params[:rule]
16+
halt 400, 'Missing rule name' unless rule
17+
18+
rule = config.rule(rule)
19+
halt 404, 'No such rule configured' unless rule
20+
21+
data = JSON.parse(request.body.read)
22+
halt 400, 'No notification body provided' unless data
23+
24+
puts 'Data:'
25+
puts data
26+
27+
client = rule.client
28+
halt 500, 'Unable to acquire Matrix client from rule' unless client
29+
30+
room = rule.room if rule.room.start_with? '!'
31+
room ||= client.join_room(rule.room)
32+
halt 500, 'Unable to acquire Matrix room from rule and client' unless room
33+
34+
plain = renderer.render_plain(data, rule)
35+
html = renderer.render_html(data, rule)
36+
37+
puts 'Plain:'
38+
puts plain
39+
40+
puts 'HTML:'
41+
puts html
42+
43+
client.send_message_event(room, 'm.room.message',
44+
msgtype: 'm.notice',
45+
body: plain,
46+
formatted_body: html,
47+
format: 'org.matrix.custom.html')
48+
49+
''
50+
end

config.yml.example

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
# Set up HS connections
3+
matrix:
4+
- name: matrix-org
5+
url: https://matrix.org
6+
access_token: <token>
7+
device_id: <device>
8+
9+
# Set up notification ingress rules
10+
rules:
11+
- name: hq # Name of the rule
12+
room: #hq:matrix.org # Room or ID
13+
matrix: matrix-org # The Matrix HS to use - defaults to first one
14+
image: true # Upload image?

grafana_matrix.gemspec

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
require File.join File.expand_path('lib', __dir__), 'grafana_matrix/version'
2+
3+
Gem::Specification.new do |spec|
4+
spec.name = 'grafana_matrix'
5+
spec.version = GrafanaMatrix::VERSION
6+
spec.authors = ['Alexander Olofsson']
7+
spec.email = ['[email protected]']
8+
9+
spec.summary = 'A Matrix notification target for Grafana'
10+
spec.description = 'Converts Grafana alerts into Matrix notifications ' \
11+
'using Grafana alert webhooks'
12+
spec.homepage = 'https://github.com/ananace/ruby-grafana-matrix'
13+
spec.license = 'MIT'
14+
15+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
16+
f.match(%r{^(test|spec|features)/})
17+
end
18+
spec.bindir = 'bin'
19+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20+
spec.require_paths = ['lib']
21+
22+
spec.add_dependency 'matrix_sdk'
23+
spec.add_dependency 'sinatra'
24+
25+
spec.add_development_dependency 'bundler'
26+
spec.add_development_dependency 'minitest'
27+
end

lib/grafana_matrix.rb

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
require 'grafana_matrix/config'
2+
require 'grafana_matrix/renderer'
3+
require 'grafana_matrix/version'
4+
5+
module GrafanaMatrix
6+
7+
end

lib/grafana_matrix/config.rb

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
require 'matrix_sdk'
2+
require 'psych'
3+
4+
module GrafanaMatrix
5+
class Config
6+
Rule = Struct.new(:config, :data) do
7+
def name
8+
data.fetch(:name)
9+
end
10+
11+
def room
12+
data.fetch(:room)
13+
end
14+
15+
def matrix
16+
data.fetch(:matrix)
17+
end
18+
19+
def image?
20+
data.fetch(:image, true)
21+
end
22+
23+
def embed_image?
24+
data.fetch(:embed_image, true)
25+
end
26+
27+
def client
28+
@client ||= config.client(matrix)
29+
end
30+
end
31+
32+
def initialize(config = {})
33+
if config.is_a? String
34+
file = config
35+
config = {}
36+
end
37+
@config = config
38+
@clients = {}
39+
40+
load!(file) if file
41+
end
42+
43+
def load!(filename = 'config.yml')
44+
raise 'No such file' unless File.exist? filename
45+
@config = Psych.load(File.read(filename))
46+
true
47+
end
48+
49+
def client(client_name = nil)
50+
client_name ||= @config['matrix'].first['name']
51+
raise 'No client name provided' unless client_name
52+
53+
client_data = @config['matrix'].find { |m| m['name'] == client_name }.dup
54+
raise 'No client configuration found for name given' unless client_data
55+
56+
# Symbolize keys
57+
client_data.keys.each do |key|
58+
client_data[(key.to_sym rescue key)] = client_data.delete key
59+
end
60+
61+
@clients[client_name] ||= begin
62+
MatrixSdk::Api.new(client_data[:url],
63+
client_data.reject { |k, _v| %i[url].include? k })
64+
end
65+
end
66+
67+
def rule(rule_name)
68+
rule_data = @config['rules'].find { |m| m['name'] == rule_name }.dup
69+
raise 'No rule configuration found for name given' if rule_data.nil?
70+
71+
# Symbolize keys
72+
rule_data.keys.each do |key|
73+
rule_data[(key.to_sym rescue key)] = rule_data.delete key
74+
end
75+
76+
Rule.new self, rule_data
77+
end
78+
end
79+
end

lib/grafana_matrix/renderer.rb

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module GrafanaMatrix
2+
class Renderer
3+
HTML_TEMPLATE = File.expand_path('templates/html.erb', __dir__).freeze
4+
PLAIN_TEMPLATE = File.expand_path('templates/plain.erb', __dir__).freeze
5+
6+
def render(data, rule, template)
7+
erb = ERB.new template, 0, '-'
8+
erb.result(binding)
9+
end
10+
11+
def render_html(data, rule, template = HTML_TEMPLATE)
12+
render data, rule, File.read(template)
13+
end
14+
15+
def render_plain(data, rule, template = PLAIN_TEMPLATE)
16+
render data, rule, File.read(template)
17+
end
18+
end
19+
end

lib/grafana_matrix/templates/html.erb

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<%-
2+
SeverityColours = {
3+
:ok => '#10a345',
4+
:paused => '#8e8e8e',
5+
:alerting => '#ed2e18',
6+
:pending => '#8e8e8e',
7+
:no_data => '#f79520',
8+
}.freeze
9+
-%>
10+
<h4><a href="<%= data['ruleUrl'] %>" style="font-weight: bold; font-style: italic; color: <%= SeverityColours[data['state'].to_sym] %>"><%= data['title'] %></a></h4>
11+
<p><%= data['message'] %></p>
12+
<%- if data['error'] -%>
13+
<h5>Error:</h5>
14+
<p><%= data['error'] %></p>
15+
<%- end -%>
16+
<%- if data['state'] != 'ok' -%>
17+
<%- unless data['evalMatches'].empty? -%>
18+
<%- data['evalMatches'].each do |match| -%>
19+
<b><%= match['metric'] %></b>: <%= match['value'] %><br/>
20+
<%- end -%>
21+
<%- end -%>
22+
<%- if data['imageUrl'] && rule.image? -%>
23+
<br/>
24+
<%- if rule.embed_image? -%>
25+
<a href="<%= data['ruleUrl'] %>"><img src="<%= data['imageUrl'] %>" alt="<%= data['ruleName'] %>" /></a>
26+
<%- else -%>
27+
<a href="<%= data['imageUrl'] %>">Image</a>
28+
<%- end -%>
29+
<%- end -%>
30+
<%- end -%>

0 commit comments

Comments
 (0)