Skip to content

Commit

Permalink
Merge pull request #227 from idealista/develop
Browse files Browse the repository at this point in the history
3.1.0 release
  • Loading branch information
blalop authored Oct 28, 2020
2 parents 754df37 + c57cf1d commit 656e5f2
Show file tree
Hide file tree
Showing 18 changed files with 300 additions and 93 deletions.
17 changes: 17 additions & 0 deletions .github/stale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a ch

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

## [3.1.0](https://github.com/idealista/prom2teams/tree/3.1.0)
[Full Changelog](https://github.com/idealista/prom2teams/compare/3.0.0...3.1.0)
### Fixed
- *[#219](https://github.com/idealista/prom2teams/pull/219) Add timeouts to webhook request to prevent hanging tcp connections in case of network errors* @DanSipola
### Added
- *[#222](https://github.com/idealista/prom2teams/pull/222) Add restrictive security context since the workload doesn't need more permissions to work.* @azman0101
- *[#226](https://github.com/idealista/prom2teams/pull/226) Retrying policy* @blalop


## [3.0.0](https://github.com/idealista/prom2teams/tree/3.0.0)
[Full Changelog](https://github.com/idealista/prom2teams/compare/2.7.0...3.0.0)
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.7-alpine AS builder
FROM python:3.8-alpine AS builder
WORKDIR /prom2teams
COPY LICENSE \
MANIFEST.in \
Expand All @@ -11,7 +11,7 @@ COPY bin/ bin
RUN apk add gcc libc-dev yaml-dev linux-headers --no-cache \
&& python setup.py bdist_wheel

FROM python:3.7-alpine
FROM python:3.8-alpine
LABEL maintainer="labs@idealista.com"
EXPOSE 8089
WORKDIR /opt/prom2teams
Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

**prom2teams** is a Web 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.

It presents grouping of alerts, labels/annotations exclusion and a Teams' alarm retry policy among its key features.


- [Getting Started](#getting-started)
- [Prerequisities](#prerequisites)
- [Installing](#installing)
Expand All @@ -32,7 +35,7 @@

### Prerequisites

The application has been tested with _Prometheus 2.2.1_, _Python 3.7.0_ and _pip 9.0.1_.
The application has been tested with _Prometheus 2.2.1_, _Python 3.8.0_ and _pip 9.0.1_.

Newer versions of _Prometheus/Python/pip_ should work but could also present issues.

Expand Down Expand Up @@ -188,7 +191,7 @@ Another approach is to provide yourself the `module` file [module example](bin/w

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:

```
```ini
[Microsoft Teams]
# At least one connector is required here
Connector: <webhook url>
Expand All @@ -214,6 +217,11 @@ Excluded: <Coma separated list of labels to ignore>

[Annotations]
Excluded: <Comma separated list of annotations to ignore>

[Teams Client]
RetryEnable: <Enables teams client retry policy> # defaults to false
RetryWaitTime: <Wait time between retries> # default: 60 secs
MaxPayload: <Teams client payload limit in bytes> # default: 24KB
```

**Note:** Grouping alerts works since v2.2.0
Expand Down Expand Up @@ -273,7 +281,7 @@ $ ./test.sh
```

## Built With
![Python 3.6.2](https://img.shields.io/badge/Python-3.6.2-green.svg)
![Python 3.8.0](https://img.shields.io/badge/Python-3.8.0-green.svg)
![pip 9.0.1](https://img.shields.io/badge/pip-9.0.1-green.svg)

## Versioning
Expand Down
4 changes: 2 additions & 2 deletions helm/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
Prom2Teams has been installed. Check its status by running:
kubectl --namespace {{ .Release.Namespace }} get pods -l "release={{ .Release.Name }}"
Prom2Teams has been installed. Check its status by running:
kubectl --namespace {{ .Release.Namespace }} get pods -l "app.kubernetes.io/instance={{ .Release.Name }}"
27 changes: 22 additions & 5 deletions helm/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,26 @@ spec:
mountPath: /opt/prom2teams/helmconfig/
env:
- name: APP_CONFIG_FILE
value: {{ .Values.prom2teams.config }}
value: {{ .Values.prom2teams.config | quote }}
- name: PROM2TEAMS_PORT
value: {{ .Values.prom2teams.port }}
value: {{ .Values.prom2teams.port | quote }}
- name: PROM2TEAMS_HOST
value: {{ .Values.prom2teams.ip }}
value: {{ .Values.prom2teams.ip | quote }}
- name: PROM2TEAMS_CONNECTOR
value: {{ .Values.prom2teams.connector }}
value: {{ .Values.prom2teams.connector | quote }}
- name: PROM2TEAMS_GROUP_ALERTS_BY
value: {{ .Values.prom2teams.group_alerts_by }}
value: {{ .Values.prom2teams.group_alerts_by | quote }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- if .Values.securityContext.enabled }}
securityContext:
privileged: false
readOnlyRootFilesystem: false
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
Expand All @@ -60,3 +69,11 @@ spec:
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.securityContext.enabled }}
securityContext:
runAsNonRoot: {{ if eq (int .Values.securityContext.runAsUser) 0 }}false{{ else }}true{{ end }}
runAsUser: {{ .Values.securityContext.runAsUser }}
runAsGroup: {{ .Values.securityContext.runAsGroup }}
fsGroup: {{ .Values.securityContext.fsGroup }}
{{- end }}

13 changes: 13 additions & 0 deletions helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,16 @@ prom2teams:
loglevel: INFO
templatepath: /opt/prom2teams/helmconfig/teams.j2
config: /opt/prom2teams/helmconfig/config.ini

# Security Context properties
securityContext:
# enabled is a flag to enable Security Context
enabled: true
# runAsUser is the user ID used to run the container
runAsUser: 65534
# runAsGroup is the primary group ID used to run all processes within any container of the pod
runAsGroup: 65534
# fsGroup is the group ID associated with the container
fsGroup: 65534
# readOnlyRootFilesystem is a flag to enable readOnlyRootFilesystem for the Hazelcast security context
readOnlyRootFilesystem: true
6 changes: 6 additions & 0 deletions prom2teams/app/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ def _config_command_line():
def _update_application_configuration(application, configuration):
if 'Microsoft Teams' in configuration:
application.config['MICROSOFT_TEAMS'] = configuration['Microsoft Teams']
if 'Microsoft Teams Client' in configuration:
application.config['TEAMS_CLIENT_CONFIG'] = {
'RETRY_ENABLE': configuration.getboolean('Microsoft Teams Client', 'RetryEnable'),
'RETRY_WAIT_TIME': configuration.getint('Microsoft Teams Client', 'RetryWaitTime'),
'MAX_PAYLOAD': configuration.getint('Microsoft Teams Client', 'MaxPayload')
}
if 'Template' in configuration and 'Path' in configuration['Template']:
application.config['TEMPLATE_PATH'] = configuration['Template']['Path']
if 'Log' in configuration and 'Level' in configuration['Log']:
Expand Down
20 changes: 8 additions & 12 deletions prom2teams/app/sender.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
import logging


from prom2teams.teams.alarm_mapper import map_and_group, map_prom_alerts_to_teams_alarms
from prom2teams.teams.composer import TemplateComposer
from .teams_client import post
from .teams_client import TeamsClient

log = logging.getLogger('prom2teams')


class AlarmSender:

def __init__(self, template_path=None, group_alerts_by=False):
def __init__(self, template_path=None, group_alerts_by=False, teams_client_config=None):
self.json_composer = TemplateComposer(template_path)
self.group_alerts_by = group_alerts_by
if template_path:
self.json_composer = TemplateComposer(template_path)
else:
self.json_composer = TemplateComposer()
self.teams_client = TeamsClient(teams_client_config)
self.max_payload = self.teams_client.max_payload_length

def _create_alarms(self, alerts):
if self.group_alerts_by:
alarms = map_and_group(alerts, self.group_alerts_by)
alarms = map_and_group(alerts, self.group_alerts_by, self.json_composer.compose, self.max_payload)
else:
alarms = map_prom_alerts_to_teams_alarms(alerts)
return self.json_composer.compose_all(alarms)

def send_alarms(self, alerts, teams_webhook_url):
sending_alarms = self._create_alarms(alerts)
for team_alarm in sending_alarms:
log.debug('The message that will be sent is: %s', str(team_alarm))
post(teams_webhook_url, team_alarm)
self.teams_client.post(teams_webhook_url, team_alarm)
67 changes: 51 additions & 16 deletions prom2teams/app/teams_client.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,55 @@
import json
import logging
import requests
from tenacity import retry, wait_fixed, after_log

from .exceptions import MicrosoftTeamsRequestException

session = requests.Session()
session.headers.update({'Content-Type': 'application/json'})


def post(teams_webhook_url, message):
response = session.post(teams_webhook_url, data=message)
if not response.ok or response.text is not '1':
exception_msg = 'Error performing request to: {}.\n' \
' Returned status code: {}.\n' \
' Returned data: {}\n' \
' Sent message: {}\n'
raise MicrosoftTeamsRequestException(exception_msg.format(teams_webhook_url,
str(response.status_code),
str(response.text),
str(message)),
code=response.status_code)
log = logging.getLogger('prom2teams')


class TeamsClient:
DEFAULT_CONFIG = {
'MAX_PAYLOAD': 24576,
'RETRY_ENABLE': False,
'RETRY_WAIT_TIME': 60
}

def __init__(self, config=None):
self.session = requests.Session()
self.session.headers.update({'Content-Type': 'application/json'})

if config is None:
config = {}
config = {**TeamsClient.DEFAULT_CONFIG, **config}
self.max_payload_length = config['MAX_PAYLOAD']
self.retry = config['RETRY_ENABLE']
self.wait_time = config['RETRY_WAIT_TIME']

def post(self, teams_webhook_url, message):
@retry(wait=wait_fixed(self.wait_time), after=after_log(log, logging.WARN))
def post_with_retry(teams_webhook_url, message):
self._do_post(teams_webhook_url, message)

def simple_post(teams_webhook_url, message):
self._do_post(teams_webhook_url, message)

log.debug('The message that will be sent is: ' + message)
if self.retry:
post_with_retry(teams_webhook_url, message)
else:
simple_post(teams_webhook_url, message)

def _do_post(self, teams_webhook_url, message):
response = self.session.post(teams_webhook_url, data=message, timeout=(5,20))
if not response.ok or response.text != '1':
exception_msg = 'Error performing request to: {}.\n' \
' Returned status code: {}.\n' \
' Returned data: {}\n' \
' Sent message: {}\n'
exception_msg.format(teams_webhook_url,
str(response.status_code),
str(response.text),
str(message))
raise MicrosoftTeamsRequestException(
exception_msg, code=response.status_code)
6 changes: 2 additions & 4 deletions prom2teams/app/versions/v1/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ class AlertReceiver(Resource):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.schema = MessageSchema()
if 'TEMPLATE_PATH' in app.config:
self.sender = AlarmSender(app.config['TEMPLATE_PATH'])
else:
self.sender = AlarmSender()
self.sender = AlarmSender(template_path=app.config.get('TEMPLATE_PATH'),
teams_client_config=app.config.get('TEAMS_CLIENT_CONFIG'))

@api_v1.expect(message)
def post(self):
Expand Down
10 changes: 5 additions & 5 deletions prom2teams/app/versions/v2/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ class AlertReceiver(Resource):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.schema = MessageSchema(exclude_fields=app.config['LABELS_EXCLUDED'], exclude_annotations=app.config['ANNOTATIONS_EXCLUDED'])
if app.config['TEMPLATE_PATH']:
self.sender = AlarmSender(app.config['TEMPLATE_PATH'], app.config['GROUP_ALERTS_BY'])
else:
self.sender = AlarmSender(group_alerts_by=app.config['GROUP_ALERTS_BY'])
self.schema = MessageSchema(exclude_fields=app.config['LABELS_EXCLUDED'],
exclude_annotations=app.config['ANNOTATIONS_EXCLUDED'])
self.sender = AlarmSender(template_path=app.config.get('TEMPLATE_PATH'),
group_alerts_by=app.config['GROUP_ALERTS_BY'],
teams_client_config=app.config.get('TEAMS_CLIENT_CONFIG'))

@api_v2.expect(message)
def post(self, connector):
Expand Down
Loading

0 comments on commit 656e5f2

Please sign in to comment.