diff --git a/promgen/management/commands/rules.py b/promgen/management/commands/rules.py index 21a4aba37..0d358632a 100644 --- a/promgen/management/commands/rules.py +++ b/promgen/management/commands/rules.py @@ -3,7 +3,6 @@ import logging -from django.conf import settings from django.core.management.base import BaseCommand from promgen import prometheus, tasks @@ -13,10 +12,6 @@ class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument('--reload', action='store_true', help='Trigger Prometheus Reload') - parser.add_argument( - '--format', type=int, dest='version', - default=settings.PROMGEN['prometheus'].get('version', 1), - help='Prometheus rule format. Defaults to promgen.yml version (%(default)s)') parser.add_argument( 'out', nargs='?', @@ -28,10 +23,9 @@ def handle(self, **kwargs): tasks.write_rules( path=kwargs['out'], reload=kwargs['reload'], - version=kwargs['version'] ) else: # Since we're already working with utf8 encoded data, we can skip # the newline ending here self.stdout.ending = None - self.stdout.write(prometheus.render_rules(version=kwargs['version'])) + self.stdout.write(prometheus.render_rules()) diff --git a/promgen/prometheus.py b/promgen/prometheus.py index 317583445..250eabd88 100644 --- a/promgen/prometheus.py +++ b/promgen/prometheus.py @@ -4,7 +4,6 @@ import datetime import json import logging -import re import subprocess import tempfile from urllib.parse import urljoin @@ -16,7 +15,6 @@ from django.conf import settings from django.core.exceptions import ValidationError from django.db.models import prefetch_related_objects -from django.template.loader import render_to_string from django.utils import timezone from promgen import models, util @@ -55,7 +53,7 @@ def check_rules(rules): raise ValidationError(rendered.decode('utf8') + e.output.decode('utf8')) -def render_rules(rules=None, version=None): +def render_rules(rules=None): ''' Render rules in a format that Prometheus understands @@ -71,8 +69,6 @@ def render_rules(rules=None, version=None): ''' if rules is None: rules = models.Rule.objects.filter(enabled=True) - if version is None: - version = settings.PROMGEN['prometheus'].get('version', 1) prefetch_related_objects( rules, @@ -84,11 +80,6 @@ def render_rules(rules=None, version=None): 'rulelabel_set', ) - # V1 format is a custom format which we render through django templates - # See promgen/tests/examples/import.rule - if version == 1: - return render_to_string('promgen/prometheus.rule', {'rules': rules}).encode('utf-8') - # V2 format is a yaml dictionary which we build and then render # See promgen/tests/examples/import.rule.yml rule_list = collections.defaultdict(list) @@ -186,6 +177,10 @@ def import_rules_v2(config, content_object=None): This assumes a dictonary in the 2.x rule format. See promgen/tests/examples/import.rule.yml for an example ''' + # If not already a dictionary, try to load as YAML + if not isinstance(config, dict): + config = yaml.safe_load(config) + counters = collections.defaultdict(int) for group in config['groups']: for r in group['rules']: @@ -223,94 +218,6 @@ def import_rules_v2(config, content_object=None): return dict(counters) -def import_rules_v1(config, content_object=None): - ''' - Parse text and extract Prometheus rules - - This assumes text in the 1.x rule format. - See promgen/tests/examples/import.rule for an example - ''' - # Attemps to match the pattern name="value" for Prometheus labels and annotations - RULE_MATCH = re.compile('((?P\w+)\s*=\s*\"(?P.*?)\")') - counters = collections.defaultdict(int) - - def parse_prom(text): - if not text: - return {} - converted = {} - for match, key, value in RULE_MATCH.findall(text.strip().strip('{}')): - converted[key] = value - return converted - - tokens = {} - rules = [] - for line in config.split('\n'): - line = line.strip() - if not line: - continue - if line.startswith('#'): - continue - - keyword, data = line.split(' ', 1) - - if keyword != 'ALERT': - tokens[keyword] = data - continue - - if keyword == 'ALERT' and 'ALERT' not in tokens: - tokens[keyword] = data - continue - - rules.append(tokens) - # Start building our next rule - tokens = {keyword: data} - # Make sure we keep our last token after parsing all lines - rules.append(tokens) - - for tokens in rules: - labels = parse_prom(tokens.get('LABELS')) - annotations = parse_prom(tokens.get('ANNOTATIONS')) - - # Check our labels to see if we have a project or service - # label set and if not, default it to a global rule - if content_object: - obj = content_object - elif 'project' in labels: - obj = models.Project.objects.get(name=labels['project']) - elif 'service' in labels: - obj = models.Service.objects.get(name=labels['service']) - else: - obj = models.Site.objects.get_current() - - rule, created = models.Rule.objects.get_or_create( - name=tokens['ALERT'], - defaults={ - 'clause': tokens['IF'], - 'duration': tokens['FOR'], - 'obj': obj, - } - ) - - if created: - counters['Rules'] += 1 - for k, v in labels.items(): - rule.add_label(k, v) - for k, v in annotations.items(): - rule.add_annotation(k, v) - - return dict(counters) - - -def import_rules(config, content_object=None): - try: - data = yaml.safe_load(config) - except Exception as e: - logger.debug('If we fail to parse yaml, then assume it is v1 format') - return import_rules_v1(config, content_object) - else: - return import_rules_v2(data, content_object) - - def import_config(config, replace_shard=None): counters = collections.defaultdict(list) skipped = collections.defaultdict(list) diff --git a/promgen/rest.py b/promgen/rest.py index 17481d97c..8162cd8e5 100644 --- a/promgen/rest.py +++ b/promgen/rest.py @@ -1,4 +1,3 @@ -from django.conf import settings from django.http import HttpResponse from promgen import filters, models, prometheus, serializers from rest_framework import viewsets @@ -22,15 +21,10 @@ def services(self, request, name): class SharedViewSet: def format(self, rules=None, name='promgen'): - version = settings.PROMGEN['prometheus'].get('version', 1) - content = prometheus.render_rules(rules, version=version) + content = prometheus.render_rules(rules) response = HttpResponse(content) - if version == 1: - response['Content-Type'] = 'text/plain; charset=utf-8' - response['Content-Disposition'] = 'attachment; filename=%s.rule' % name - else: - response['Content-Type'] = 'application/x-yaml' - response['Content-Disposition'] = 'attachment; filename=%s.rule.yml' % name + response['Content-Type'] = 'application/x-yaml' + response['Content-Disposition'] = 'attachment; filename=%s.rule.yml' % name return response diff --git a/promgen/tasks.py b/promgen/tasks.py index 43a2213df..b656a8f2d 100644 --- a/promgen/tasks.py +++ b/promgen/tasks.py @@ -105,12 +105,12 @@ def write_config(path=None, reload=True, chmod=0o644): @shared_task -def write_rules(path=None, reload=True, chmod=0o644, version=None): +def write_rules(path=None, reload=True, chmod=0o644): if path is None: path = settings.PROMGEN["prometheus"]["rules"] with atomic_write(path, mode="wb", overwrite=True) as fp: # Set mode on our temporary file before we write and move it os.chmod(fp.name, chmod) - fp.write(prometheus.render_rules(version=version)) + fp.write(prometheus.render_rules()) if reload: reload_prometheus() diff --git a/promgen/templates/promgen/prometheus.rule b/promgen/templates/promgen/prometheus.rule deleted file mode 100644 index 7dacbe399..000000000 --- a/promgen/templates/promgen/prometheus.rule +++ /dev/null @@ -1,7 +0,0 @@ -{% load promgen %}{% for rule in rules %}{% autoescape off %}ALERT {{ rule.name }} - IF {{ rule.clause|rulemacro:rule }} - FOR {{ rule.duration}} -{% if rule.labels %} LABELS {{ rule.labels|to_prom }} -{% endif %}{% if rule.annotations %} ANNOTATIONS {{ rule.annotations|to_prom }} -{% endif %}{% endautoescape %} -{% endfor %} diff --git a/promgen/tests/examples/import.rule b/promgen/tests/examples/import.rule deleted file mode 100644 index 441af6242..000000000 --- a/promgen/tests/examples/import.rule +++ /dev/null @@ -1,18 +0,0 @@ -# Service: -# Service URL: /service/49/ -ALERT ExporterDown - IF up == 0 - FOR 1m - LABELS {severity="major"} - ANNOTATIONS {bar="baz", service="http://promgen.example/service/49/", summary="High load on {{ $labels.instance }}"} - - - - -# Service: -# Service URL: /service/49/ -ALERT ExporterTargetDown - IF {__name__=~".*_up"} == 0 - FOR 1m - LABELS {severity="warning", another="foo"} - ANNOTATIONS {bar="baz", service="http://promgen.example/service/49/", summary="High load on {{ $labels.instance }}"} diff --git a/promgen/tests/test_rules.py b/promgen/tests/test_rules.py index 3adfac2de..58ac4db2b 100644 --- a/promgen/tests/test_rules.py +++ b/promgen/tests/test_rules.py @@ -12,17 +12,7 @@ from django.test import override_settings from django.urls import reverse -_RULES = ''' -ALERT RuleName - IF up==0 - FOR 1s - LABELS {severity="severe"} - ANNOTATIONS {rule="http://example.com/rule/%d/edit", summary="Test case"} - - -'''.lstrip().encode('utf-8') - -_RULE_NEW = ''' +_RULE_V2 = ''' groups: - name: example.com rules: @@ -56,15 +46,10 @@ def setUp(self, mock_signal): models.RuleLabel.objects.create(name='severity', value='severe', rule=self.rule) models.RuleAnnotation.objects.create(name='summary', value='Test case', rule=self.rule) - @mock.patch('django.dispatch.dispatcher.Signal.send') - def test_write_old(self, mock_post): - result = prometheus.render_rules(version=1) - self.assertEqual(result, _RULES % self.rule.id) - @mock.patch('django.dispatch.dispatcher.Signal.send') def test_write_new(self, mock_post): - result = prometheus.render_rules(version=2) - self.assertEqual(result, _RULE_NEW % self.rule.id) + result = prometheus.render_rules() + self.assertEqual(result, _RULE_V2 % self.rule.id) @mock.patch('django.dispatch.dispatcher.Signal.send') def test_copy(self, mock_post): @@ -77,21 +62,6 @@ def test_copy(self, mock_post): self.assertEqual(models.RuleLabel.objects.count(), 3, 'Copied rule has exiting labels + service label') self.assertEqual(models.RuleAnnotation.objects.count(), 2) - @mock.patch('django.dispatch.dispatcher.Signal.send') - def test_import_v1(self, mock_post): - self.user.user_permissions.add( - Permission.objects.get(codename='change_rule'), - Permission.objects.get(codename='change_site', content_type__app_label='sites'), - ) - self.client.post(reverse('rule-import'), { - 'rules': PromgenTest.data('examples', 'import.rule') - }) - - # Includes count of our setUp rule + imported rules - self.assertEqual(models.Rule.objects.count(), 3, 'Missing Rule') - self.assertEqual(models.RuleLabel.objects.count(), 4, 'Missing labels') - self.assertEqual(models.RuleAnnotation.objects.count(), 7, 'Missing annotations') - @mock.patch('django.dispatch.dispatcher.Signal.send') def test_import_v2(self, mock_post): self.user.user_permissions.add( diff --git a/promgen/views.py b/promgen/views.py index c0e8181fe..5f98b79db 100644 --- a/promgen/views.py +++ b/promgen/views.py @@ -863,7 +863,7 @@ def post(self, request, content_type, object_id): service = get_object_or_404(models.Service, id=self.kwargs['pk']) if importform.is_valid(): data = importform.clean() - counters = prometheus.import_rules(data['rules'], service) + counters = prometheus.import_rules_v2(data['rules'], service) messages.info(request, 'Imported %s' % counters) return HttpResponseRedirect(service.get_absolute_url()) @@ -996,15 +996,10 @@ def post(self, request): class _ExportRules(View): def format(self, rules=None, name='promgen'): - version = settings.PROMGEN['prometheus'].get('version', 1) - content = prometheus.render_rules(rules, version=version) + content = prometheus.render_rules(rules) response = HttpResponse(content) - if version == 1: - response['Content-Type'] = 'text/plain; charset=utf-8' - response['Content-Disposition'] = 'attachment; filename=%s.rule' % name - else: - response['Content-Type'] = 'application/x-yaml' - response['Content-Disposition'] = 'attachment; filename=%s.rule.yml' % name + response['Content-Type'] = 'application/x-yaml' + response['Content-Disposition'] = 'attachment; filename=%s.rule.yml' % name return response @@ -1152,7 +1147,6 @@ class RuleImport(PromgenPermissionMixin, FormView): permission_required = ('sites.change_site', 'promgen.change_rule') permisison_denied_message = 'User lacks permission to import' - def form_valid(self, form): data = form.clean() if data.get('file_field'): @@ -1164,7 +1158,7 @@ def form_valid(self, form): return self.form_invalid(form) try: - counters = prometheus.import_rules(rules) + counters = prometheus.import_rules_v2(rules) messages.info(self.request, 'Imported %s' % counters) return redirect('rule-import') except: