Skip to content

Commit

Permalink
Merge pull request #14 from idealista/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
dortegau authored Aug 28, 2017
2 parents 2f52c52 + 1225d7f commit 32e78ae
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language: python
python:
- "3.6"
install: "pip install -r requirements.txt"
script: python -m unittest discover tests
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a ch

## [Unreleased](https://github.com/idealista/prom2teams/tree/develop)

## [1.1.0](https://github.com/idealista/prom2teams/tree/1.1.0)
[Full Changelog](https://github.com/idealista/prom2teams/compare/1.0.0...1.1.0)
### Added
- *[#5](https://github.com/idealista/prom2teams/issues/5) Allow to provide log file path and log level as arguments* @dortegau

### Fixed
- *[#6](https://github.com/idealista/prom2teams/issues/6) Allow to define previously declared default values as blank values in provided config* @dortegau
- *[#8](https://github.com/idealista/prom2teams/issues/8) Closing all file descriptors and adding some unit tests* @dortegau
- *[#10](https://github.com/idealista/prom2teams/issues/10) Capturing Keyboard Interrupt and logging server stop event* @dortegau

## [1.0.0](https://github.com/idealista/prom2teams/tree/1.0.0)
### Added
Expand Down
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
![Logo](https://raw.githubusercontent.com/idealista/prom2teams/master/logo.gif)

[![Build Status](https://travis-ci.org/idealista/prom2teams.png)](https://travis-ci.org/idealista/prom2teams)

# prom2teams

<img src="assets/example.png" alt="Alert example" style="width: 600px;"/>

**prom2teams** is a HTTP server built with Python that receives alert notifications from a previously configured [Prometheus Alertmanager](https://github.com/prometheus/alertmanager) instance and forwards it to [Microsoft Teams](https://teams.microsoft.com/) using defined connectors.
**prom2teams** is an HTTP server built with Python that receives alert notifications from a previously configured [Prometheus Alertmanager](https://github.com/prometheus/alertmanager) instance and forwards it to [Microsoft Teams](https://teams.microsoft.com/) using defined connectors.

- [Getting Started](#getting-started)
- [Prerequisities](#prerequisities)
Expand All @@ -13,6 +15,7 @@
- [Config file](#config-file)
- [Configuring Prometheus](#configuring-prometheus)
- [Templating](#templating)
- [Testing](#testing)
- [Built With](#built-with)
- [Versioning](#versioning)
- [Authors](#authors)
Expand All @@ -38,13 +41,15 @@ $ pip3 install prom2teams
## Usage

```bash
# To start the server (a config file path must be provided, Jinja2 template is optional):
$ prom2teams start --configpath <config file path> [--templatepath <Jinja2 template file path>]
# To start the server (a config file path must be provided, log file path, log level and Jinja2 template path are optional arguments):
$ prom2teams start --configpath <config file path> [--logfilepath <log file path>] [--loglevel (DEBUG|INFO|WARNING|ERROR|CRITICAL)] [--templatepath <Jinja2 template file path>]

# To show the help message:
$ prom2teams --help
```

**Note:** default log level is INFO. Messages are redirected to stdout if no log file path is provided.

### Config file

The config file is an [INI file](https://docs.python.org/3/library/configparser.html#supported-ini-file-structure) and should have the structure described below:
Expand Down Expand Up @@ -73,6 +78,15 @@ url: 0.0.0.0:8089

prom2teams provides a [default template](app/teams/template.j2) built with [Jinja2](http://jinja.pocoo.org/docs/2.9/) to render messages in Microsoft Teams. This template could be overrided using the 'templatepath' argument ('--templatepath <Jinja2 template file path>') during the application start.

## Testing

To run the test suite you should type the following:

```bash
# After cloning prom2 teams :)
$ python3 -m unittest discover tests
```

## Built With
![Python 3.6.2](https://img.shields.io/badge/Python-3.6.2-green.svg)
![pip 9.0.1](https://img.shields.io/badge/pip-9.0.1-green.svg)
Expand Down
47 changes: 37 additions & 10 deletions app/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,19 @@ def do_POST(self):
except Exception as e:
logger.error('Error processing request: %s', str(e))
self.send_error(500, 'Error processing request')

def log_message(self, format, *args):
logger.info("%s - - [%s] %s" % (self.address_string(),
self.log_date_time_string(),
format % args))

return PrometheusRequestHandler


def run(config_file, template_path):
config = get_config(config_file)
def run(provided_config_file, template_path, log_file_path, log_level):
config = get_config('config.ini', provided_config_file)

fileConfig('logging_config.ini')
load_logging_config(log_file_path, log_level)

host = config['HTTP Server']['Host']
port = int(config['HTTP Server']['Port'])
Expand All @@ -56,16 +62,37 @@ def run(config_file, template_path):
config['Microsoft Teams']['Connector'],
template_path)
httpd = HTTPServer(server_address, request_handler)
httpd.serve_forever()

try:
httpd.serve_forever()
except KeyboardInterrupt:
logger.info('server stopped')

httpd.server_close()


def load_logging_config(log_file_path, log_level):
config_file = 'logging_console_config.ini'
defaults = {'log_level': log_level}

if(log_file_path):
config_file = 'logging_file_config.ini'
defaults = {
'log_level': log_level,
'log_file_path': log_file_path
}

fileConfig(config_file, defaults=defaults)


def get_config(default_config_file, provided_config_file):
provided_config = configparser.ConfigParser()

def get_config(provided_config_file):
default_config = configparser.ConfigParser()
default_config.read_file(open('config.ini'))
default_sections = default_config._sections
with open(default_config_file) as f_def:
provided_config.read_file(f_def)

provided_config = configparser.ConfigParser(defaults=default_sections)
provided_config.read_file(open(provided_config_file))
with open(provided_config_file) as f_prov:
provided_config.read_file(f_prov)

try:
provided_config['Microsoft Teams']['Connector']
Expand Down
4 changes: 3 additions & 1 deletion bin/prom2teams
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ if __name__ == "__main__":
'and sends it to Microsoft Teams using configured connectors ')

parser.add_argument('-c', '--configpath', help='config INI file path', required=True)
parser.add_argument('-l', '--logfilepath', help='log file path', required=False)
parser.add_argument('-v', '--loglevel', help='log level', required=False, default='INFO')
parser.add_argument('-t', '--templatepath', help='Jinja2 template file path', required=False)

args = parser.parse_args()

run(args.configpath, args.templatepath)
run(args.configpath, args.templatepath, args.logfilepath, args.loglevel)
21 changes: 21 additions & 0 deletions logging_console_config.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[loggers]
keys=root

[handlers]
keys=stream_handler

[formatters]
keys=formatter

[logger_root]
level=%(log_level)s
handlers=stream_handler

[handler_stream_handler]
class=StreamHandler
level=%(log_level)s
formatter=formatter
args=(sys.stdout,)

[formatter_formatter]
format=%(asctime)s %(name)-4s %(levelname)-4s %(message)s
6 changes: 3 additions & 3 deletions logging_config.ini → logging_file_config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ keys=file_handler
keys=formatter

[logger_root]
level=INFO
level=%(log_level)s
handlers=file_handler

[handler_file_handler]
class=FileHandler
args=('debug.log',)
level=INFO
args=('%(log_file_path)s',)
level=%(log_level)s
formatter=formatter

[formatter_formatter]
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def read_requirements_file():


setup(name='prom2teams',
version='1.0.0',
version='1.1.0',
description='Project that redirects Prometheus Alert Manager '
'notifications to Microsoft Teams',
long_description=readme,
Expand Down
7 changes: 7 additions & 0 deletions tests/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import os
import sys

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../app')))

import server
import exceptions
Empty file added tests/data/empty_config.ini
Empty file.
6 changes: 6 additions & 0 deletions tests/data/overriding_defaults.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[HTTP Server]
Host: 1.1.1.1
Port: 9089

[Microsoft Teams]
Connector=some_url
2 changes: 2 additions & 0 deletions tests/data/without_overriding_defaults.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[Microsoft Teams]
Connector=some_url
51 changes: 51 additions & 0 deletions tests/test_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import unittest

from context import server
from context import exceptions


class TestServer(unittest.TestCase):

TEST_CONFIG_FILES_PATH = 'tests/data/'
DEFAULT_CONFIG_RELATIVE_PATH = './config.ini'

def test_get_config_with_invalid_path(self):
invalid_relative_path = self.TEST_CONFIG_FILES_PATH + 'invalid_path'

self.assertRaises(FileNotFoundError,
server.get_config,
self.DEFAULT_CONFIG_RELATIVE_PATH,
invalid_relative_path)

def test_get_config_without_required_keys_should_raise_exception(self):
empty_config_relative_path = self.TEST_CONFIG_FILES_PATH + \
'empty_config.ini'

self.assertRaises(exceptions.MissingConnectorConfigKeyException,
server.get_config,
self.DEFAULT_CONFIG_RELATIVE_PATH,
empty_config_relative_path)

def test_get_config_without_override(self):
provided_config_relative_path = self.TEST_CONFIG_FILES_PATH + \
'without_overriding_defaults.ini'
config = server.get_config(self.DEFAULT_CONFIG_RELATIVE_PATH,
provided_config_relative_path)

self.assertEqual(config.get('HTTP Server', 'Host'), '0.0.0.0')
self.assertEqual(config.get('HTTP Server', 'Port'), '8089')
self.assertTrue(config.get('Microsoft Teams', 'Connector'))

def test_get_config_overriding_defaults(self):
provided_config_relative_path = self.TEST_CONFIG_FILES_PATH + \
'overriding_defaults.ini'
config = server.get_config(self.DEFAULT_CONFIG_RELATIVE_PATH,
provided_config_relative_path)

self.assertEqual(config.get('HTTP Server', 'Host'), '1.1.1.1')
self.assertEqual(config.get('HTTP Server', 'Port'), '9089')
self.assertTrue(config.get('Microsoft Teams', 'Connector'))


if __name__ == '__main__':
unittest.main()

0 comments on commit 32e78ae

Please sign in to comment.