diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..bf9db52
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,5 @@
+language: python
+python:
+ - "3.6"
+install: "pip install -r requirements.txt"
+script: python -m unittest discover tests
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c368cc3..02c0775 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/README.md b/README.md
index bc4d438..97f6d25 100644
--- a/README.md
+++ b/README.md
@@ -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
-**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)
@@ -13,6 +15,7 @@
- [Config file](#config-file)
- [Configuring Prometheus](#configuring-prometheus)
- [Templating](#templating)
+- [Testing](#testing)
- [Built With](#built-with)
- [Versioning](#versioning)
- [Authors](#authors)
@@ -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 [--templatepath ]
+# 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 [--logfilepath ] [--loglevel (DEBUG|INFO|WARNING|ERROR|CRITICAL)] [--templatepath ]
# 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:
@@ -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 ') 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)
diff --git a/app/server.py b/app/server.py
index 018d79a..6196a7f 100644
--- a/app/server.py
+++ b/app/server.py
@@ -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'])
@@ -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']
diff --git a/bin/prom2teams b/bin/prom2teams
index d0378f3..8d86284 100755
--- a/bin/prom2teams
+++ b/bin/prom2teams
@@ -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)
diff --git a/logging_console_config.ini b/logging_console_config.ini
new file mode 100644
index 0000000..1f3f403
--- /dev/null
+++ b/logging_console_config.ini
@@ -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
diff --git a/logging_config.ini b/logging_file_config.ini
similarity index 79%
rename from logging_config.ini
rename to logging_file_config.ini
index b36b31e..d994937 100644
--- a/logging_config.ini
+++ b/logging_file_config.ini
@@ -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]
diff --git a/setup.py b/setup.py
index 8d902f5..d6563a2 100644
--- a/setup.py
+++ b/setup.py
@@ -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,
diff --git a/tests/context.py b/tests/context.py
new file mode 100644
index 0000000..2a7a196
--- /dev/null
+++ b/tests/context.py
@@ -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
diff --git a/tests/data/empty_config.ini b/tests/data/empty_config.ini
new file mode 100644
index 0000000..e69de29
diff --git a/tests/data/overriding_defaults.ini b/tests/data/overriding_defaults.ini
new file mode 100644
index 0000000..6d666c9
--- /dev/null
+++ b/tests/data/overriding_defaults.ini
@@ -0,0 +1,6 @@
+[HTTP Server]
+Host: 1.1.1.1
+Port: 9089
+
+[Microsoft Teams]
+Connector=some_url
diff --git a/tests/data/without_overriding_defaults.ini b/tests/data/without_overriding_defaults.ini
new file mode 100644
index 0000000..a0a37e1
--- /dev/null
+++ b/tests/data/without_overriding_defaults.ini
@@ -0,0 +1,2 @@
+[Microsoft Teams]
+Connector=some_url
diff --git a/tests/test_server.py b/tests/test_server.py
new file mode 100644
index 0000000..d39828b
--- /dev/null
+++ b/tests/test_server.py
@@ -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()