#!/usr/bin/env python3 """Fetch alerting and aggregation rules from provided urls into this chart.""" import json import os import re import shutil import subprocess import textwrap import _jsonnet import requests import yaml from yaml.representer import SafeRepresenter # https://stackoverflow.com/a/20863889/961092 class LiteralStr(str): pass def change_style(style, representer): def new_representer(dumper, data): scalar = representer(dumper, data) scalar.style = style return scalar return new_representer refs = { # renovate: git-refs=https://github.com/prometheus-operator/kube-prometheus branch=main 'ref.kube-prometheus': '1e4df581de8897f16108bc4881be26e2a98c02b8', # renovate: git-refs=https://github.com/kubernetes-monitoring/kubernetes-mixin branch=master 'ref.kubernetes-mixin': '6c82d5abe587b4c1dda7f1b0013af7d81e84c9fe', # renovate: git-refs=https://github.com/etcd-io/etcd branch=main 'ref.etcd': '592c195ae21e8d58b7e2fef355e7067499d70edd', } # Source files list charts = [ { 'git': 'https://github.com/prometheus-operator/kube-prometheus.git', 'branch': refs['ref.kube-prometheus'], 'source': 'main.libsonnet', 'cwd': '', 'destination': '../templates/prometheus/rules-1.14', 'min_kubernetes': '1.14.0-0', 'mixin': """ local kp = (import 'jsonnet/kube-prometheus/main.libsonnet') + { values+:: { nodeExporter+: { mixin+: { _config+: { fsSelector: '$.Values.defaultRules.node.fsSelector', }, }, }, common+: { namespace: 'monitoring', }, kubernetesControlPlane+: { kubeProxy: true, }, }, grafana: {}, }; { groups: std.flattenArrays([ kp[component][resource].spec.groups for component in std.objectFields(kp) for resource in std.filter( function(resource) kp[component][resource].kind == 'PrometheusRule', std.objectFields(kp[component]) ) ]), } """ }, { 'git': 'https://github.com/kubernetes-monitoring/kubernetes-mixin.git', 'branch': refs['ref.kubernetes-mixin'], 'source': 'windows.libsonnet', 'cwd': 'rules', 'destination': '../templates/prometheus/rules-1.14', 'min_kubernetes': '1.14.0-0', 'mixin': """ local kp = { prometheusAlerts+:: {}, prometheusRules+:: {}} + (import "windows.libsonnet") + {'_config': { 'clusterLabel': 'cluster', 'windowsExporterSelector': 'job="windows-exporter"', 'kubeStateMetricsSelector': 'job="kube-state-metrics"', }}; kp.prometheusAlerts + kp.prometheusRules """ }, { 'git': 'https://github.com/etcd-io/etcd.git', 'branch': refs['ref.etcd'], 'source': 'mixin.libsonnet', 'cwd': 'contrib/mixin', 'destination': '../templates/prometheus/rules-1.14', 'min_kubernetes': '1.14.0-0', # Override the default etcd_instance_labels to get proper aggregation for etcd instances in k8s clusters (#2720) # see https://github.com/etcd-io/etcd/blob/1c22e7b36bc5d8543f1646212f2960f9fe503b8c/contrib/mixin/config.libsonnet#L13 'mixin': """ local kp = { prometheusAlerts+:: {}, prometheusRules+:: {}} + (import "mixin.libsonnet") + {'_config': { 'etcd_selector': 'job=~".*etcd.*"', 'etcd_instance_labels': 'instance, pod', 'scrape_interval_seconds': 30, 'clusterLabel': 'job', }}; kp.prometheusAlerts + kp.prometheusRules """ }, ] # Additional conditions map condition_map = { 'alertmanager.rules': ' .Values.defaultRules.rules.alertmanager', 'config-reloaders': ' .Values.defaultRules.rules.configReloaders', 'etcd': ' .Values.kubeEtcd.enabled .Values.defaultRules.rules.etcd', 'general.rules': ' .Values.defaultRules.rules.general', 'k8s.rules.container_cpu_limits': ' .Values.defaultRules.rules.k8sContainerCpuLimits', 'k8s.rules.container_cpu_requests': ' .Values.defaultRules.rules.k8sContainerCpuRequests', 'k8s.rules.container_cpu_usage_seconds_total': ' .Values.defaultRules.rules.k8sContainerCpuUsageSecondsTotal', 'k8s.rules.container_memory_cache': ' .Values.defaultRules.rules.k8sContainerMemoryCache', 'k8s.rules.container_memory_limits': ' .Values.defaultRules.rules.k8sContainerMemoryLimits', 'k8s.rules.container_memory_requests': ' .Values.defaultRules.rules.k8sContainerMemoryRequests', 'k8s.rules.container_memory_rss': ' .Values.defaultRules.rules.k8sContainerMemoryRss', 'k8s.rules.container_memory_swap': ' .Values.defaultRules.rules.k8sContainerMemorySwap', 'k8s.rules.container_memory_working_set_bytes': ' .Values.defaultRules.rules.k8sContainerMemoryWorkingSetBytes', 'k8s.rules.container_resource': ' .Values.defaultRules.rules.k8sContainerResource', 'k8s.rules.pod_owner': ' .Values.defaultRules.rules.k8sPodOwner', 'kube-apiserver-availability.rules': ' .Values.kubeApiServer.enabled .Values.defaultRules.rules.kubeApiserverAvailability', 'kube-apiserver-burnrate.rules': ' .Values.kubeApiServer.enabled .Values.defaultRules.rules.kubeApiserverBurnrate', 'kube-apiserver-histogram.rules': ' .Values.kubeApiServer.enabled .Values.defaultRules.rules.kubeApiserverHistogram', 'kube-apiserver-slos': ' .Values.kubeApiServer.enabled .Values.defaultRules.rules.kubeApiserverSlos', 'kube-prometheus-general.rules': ' .Values.defaultRules.rules.kubePrometheusGeneral', 'kube-prometheus-node-recording.rules': ' .Values.defaultRules.rules.kubePrometheusNodeRecording', 'kube-scheduler.rules': ' .Values.kubeScheduler.enabled .Values.defaultRules.rules.kubeSchedulerRecording', 'kube-state-metrics': ' .Values.defaultRules.rules.kubeStateMetrics', 'kubelet.rules': ' .Values.kubelet.enabled .Values.defaultRules.rules.kubelet', 'kubernetes-apps': ' .Values.defaultRules.rules.kubernetesApps', 'kubernetes-resources': ' .Values.defaultRules.rules.kubernetesResources', 'kubernetes-storage': ' .Values.defaultRules.rules.kubernetesStorage', 'kubernetes-system': ' .Values.defaultRules.rules.kubernetesSystem', 'kubernetes-system-kube-proxy': ' .Values.kubeProxy.enabled .Values.defaultRules.rules.kubeProxy', 'kubernetes-system-apiserver': ' .Values.defaultRules.rules.kubernetesSystem', # kubernetes-system was split into more groups in 1.14, one of them is kubernetes-system-apiserver 'kubernetes-system-kubelet': ' .Values.defaultRules.rules.kubernetesSystem', # kubernetes-system was split into more groups in 1.14, one of them is kubernetes-system-kubelet 'kubernetes-system-controller-manager': ' .Values.kubeControllerManager.enabled .Values.defaultRules.rules.kubeControllerManager', 'kubernetes-system-scheduler': ' .Values.kubeScheduler.enabled .Values.defaultRules.rules.kubeSchedulerAlerting', 'node-exporter.rules': ' .Values.defaultRules.rules.nodeExporterRecording', 'node-exporter': ' .Values.defaultRules.rules.nodeExporterAlerting', 'node.rules': ' .Values.defaultRules.rules.node', 'node-network': ' .Values.defaultRules.rules.network', 'prometheus-operator': ' .Values.defaultRules.rules.prometheusOperator', 'prometheus': ' .Values.defaultRules.rules.prometheus', # kube-prometheus >= 1.14 uses prometheus as group instead of prometheus.rules 'windows.node.rules': ' .Values.windowsMonitoring.enabled .Values.defaultRules.rules.windows', 'windows.pod.rules': ' .Values.windowsMonitoring.enabled .Values.defaultRules.rules.windows', } alert_condition_map = { 'AggregatedAPIDown': 'semverCompare ">=1.18.0-0" $kubeTargetVersion', 'AlertmanagerDown': '.Values.alertmanager.enabled', 'CoreDNSDown': '.Values.kubeDns.enabled', 'KubeAPIDown': '.Values.kubeApiServer.enabled', # there are more alerts which are left enabled, because they'll never fire without metrics 'KubeControllerManagerDown': '.Values.kubeControllerManager.enabled', 'KubeletDown': '.Values.prometheusOperator.kubeletService.enabled', # there are more alerts which are left enabled, because they'll never fire without metrics 'KubeSchedulerDown': '.Values.kubeScheduler.enabled', 'KubeStateMetricsDown': '.Values.kubeStateMetrics.enabled', # there are more alerts which are left enabled, because they'll never fire without metrics 'NodeExporterDown': '.Values.nodeExporter.enabled', 'PrometheusOperatorDown': '.Values.prometheusOperator.enabled', } replacement_map = { 'job="prometheus-operator"': { 'replacement': 'job="{{ $operatorJob }}"', 'init': '{{- $operatorJob := printf "%s-%s" (include "kube-prometheus-stack.fullname" .) "operator" }}'}, 'job="prometheus-k8s"': { 'replacement': 'job="{{ $prometheusJob }}"', 'init': '{{- $prometheusJob := printf "%s-%s" (include "kube-prometheus-stack.fullname" .) "prometheus" }}'}, 'job="alertmanager-main"': { 'replacement': 'job="{{ $alertmanagerJob }}"', 'init': '{{- $alertmanagerJob := printf "%s-%s" (include "kube-prometheus-stack.fullname" .) "alertmanager" }}'}, 'namespace="monitoring"': { 'replacement': 'namespace="{{ $namespace }}"', 'init': '{{- $namespace := printf "%s" (include "kube-prometheus-stack.namespace" .) }}'}, 'alertmanager-$1': { 'replacement': '$1', 'init': ''}, 'job="kube-state-metrics"': { 'replacement': 'job="{{ $kubeStateMetricsJob }}"', 'init': '{{- $kubeStateMetricsJob := include "kube-prometheus-stack-kube-state-metrics.name" . }}'}, 'job="{{ $kubeStateMetricsJob }}"': { 'replacement': 'job="{{ $kubeStateMetricsJob }}", namespace=~"{{ $targetNamespace }}"', 'limitGroup': ['kubernetes-apps'], 'init': '{{- $targetNamespace := .Values.defaultRules.appNamespacesTarget }}'}, 'job="kubelet"': { 'replacement': 'job="kubelet", namespace=~"{{ $targetNamespace }}"', 'limitGroup': ['kubernetes-storage'], 'init': '{{- $targetNamespace := .Values.defaultRules.appNamespacesTarget }}'}, 'runbook_url: https://runbooks.prometheus-operator.dev/runbooks/': { 'replacement': 'runbook_url: {{ .Values.defaultRules.runbookUrl }}/', 'init': ''}, '(namespace,service)': { 'replacement': '(namespace,service,cluster)', 'init': ''}, '(namespace, job, handler': { 'replacement': '(cluster, namespace, job, handler', 'init': ''}, '$.Values.defaultRules.node.fsSelector': { 'replacement': '{{ $.Values.defaultRules.node.fsSelector }}', 'init': ''}, } # standard header header = '''{{- /* Generated from '%(name)s' group from %(url)s Do not change in-place! In order to change this file first read following link: https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack/hack */ -}} {{- $kubeTargetVersion := default .Capabilities.KubeVersion.GitVersion .Values.kubeTargetVersionOverride }} {{- if and (semverCompare ">=%(min_kubernetes)s" $kubeTargetVersion) (semverCompare "<%(max_kubernetes)s" $kubeTargetVersion) .Values.defaultRules.create%(condition)s }}%(init_line)s apiVersion: monitoring.coreos.com/v1 kind: PrometheusRule metadata: name: {{ printf "%%s-%%s" (include "kube-prometheus-stack.fullname" .) "%(name)s" | trunc 63 | trimSuffix "-" }} namespace: {{ template "kube-prometheus-stack.namespace" . }} labels: app: {{ template "kube-prometheus-stack.name" . }} {{ include "kube-prometheus-stack.labels" . | indent 4 }} {{- if .Values.defaultRules.labels }} {{ toYaml .Values.defaultRules.labels | indent 4 }} {{- end }} {{- if .Values.defaultRules.annotations }} annotations: {{ toYaml .Values.defaultRules.annotations | indent 4 }} {{- end }} spec: groups: -''' def init_yaml_styles(): represent_literal_str = change_style('|', SafeRepresenter.represent_str) yaml.add_representer(LiteralStr, represent_literal_str) def escape(s): return s.replace("{{", "{{`{{").replace("}}", "}}`}}").replace("{{`{{", "{{`{{`}}").replace("}}`}}", "{{`}}`}}") def fix_expr(rules): """Remove trailing whitespaces and line breaks, which happen to creep in due to yaml import specifics; convert multiline expressions to literal style, |-""" for rule in rules: rule['expr'] = rule['expr'].rstrip() if '\n' in rule['expr']: rule['expr'] = LiteralStr(rule['expr']) def yaml_str_repr(struct, indent=4): """represent yaml as a string""" text = yaml.dump( struct, width=1000, # to disable line wrapping default_flow_style=False # to disable multiple items on single line ) text = escape(text) # escape {{ and }} for helm text = textwrap.indent(text, ' ' * indent)[indent - 1:] # indent everything, and remove very first line extra indentation return text def get_rule_group_condition(group_name, value_key): if group_name == '': return '' if group_name.count(".Values") > 1: group_name = group_name.split(' ')[-1] return group_name.replace('Values.defaultRules.rules', f"Values.defaultRules.{value_key}").strip() def add_rules_conditions(rules, rules_map, indent=4): """Add if wrapper for rules, listed in rules_map""" rule_condition = '{{- if %s }}\n' for alert_name in rules_map: line_start = ' ' * indent + '- alert: ' if line_start + alert_name in rules: rule_text = rule_condition % rules_map[alert_name] start = 0 # to modify all alerts with same name while True: try: # add if condition index = rules.index(line_start + alert_name, start) start = index + len(rule_text) + 1 rules = rules[:index] + rule_text + rules[index:] # add end of if try: next_index = rules.index(line_start, index + len(rule_text) + 1) except ValueError: # we found the last alert in file if there are no alerts after it next_index = len(rules) # depending on the rule ordering in rules_map it's possible that an if statement from another rule is present at the end of this block. found_block_end = False last_line_index = next_index while not found_block_end: last_line_index = rules.rindex('\n', index, last_line_index - 1) # find the starting position of the last line last_line = rules[last_line_index + 1:next_index] if last_line.startswith('{{- if'): next_index = last_line_index + 1 # move next_index back if the current block ends in an if statement continue found_block_end = True rules = rules[:next_index] + '{{- end }}\n' + rules[next_index:] except ValueError: break return rules def add_rules_conditions_from_condition_map(rules, indent=4): """Add if wrapper for rules, listed in alert_condition_map""" rules = add_rules_conditions(rules, alert_condition_map, indent) return rules def add_rules_per_rule_conditions(rules, group, indent=4): """Add if wrapper for rules, listed in alert_condition_map""" rules_condition_map = {} for rule in group['rules']: if 'alert' in rule: rules_condition_map[rule['alert']] = f"not (.Values.defaultRules.disabled.{rule['alert']} | default false)" rules = add_rules_conditions(rules, rules_condition_map, indent) return rules def add_custom_labels(rules_str, group, indent=4, label_indent=2): """Add if wrapper for additional rules labels""" rule_group_labels = get_rule_group_condition(condition_map.get(group['name'], ''), 'additionalRuleGroupLabels') additional_rule_labels = textwrap.indent(""" {{- with .Values.defaultRules.additionalRuleLabels }} {{- toYaml . | nindent 8 }} {{- end }} {{- with %s }} {{- toYaml . | nindent 8 }} {{- end }}""" % (rule_group_labels,), " " * (indent + label_indent * 2)) additional_rule_labels_condition_start = "\n" + " " * (indent + label_indent) + '{{- if or .Values.defaultRules.additionalRuleLabels %s }}' % (rule_group_labels,) additional_rule_labels_condition_end = "\n" + " " * (indent + label_indent) + '{{- end }}' # labels: cannot be null, if a rule does not have any labels by default, the labels block # should only be added if there are .Values defaultRules.additionalRuleLabels defined rule_seperator = "\n" + " " * indent + "-.*" label_seperator = "\n" + " " * indent + " labels:" section_seperator = "\n" + " " * indent + " \\S" section_seperator_len = len(section_seperator)-1 rules_positions = re.finditer(rule_seperator,rules_str) # fetch breakpoint between each set of rules ruleStartingLine = [(rule_position.start(),rule_position.end()) for rule_position in rules_positions] head = rules_str[:ruleStartingLine[0][0]] # construct array of rules so they can be handled individually rules = [] # pylint: disable=E1136 # See https://github.com/pylint-dev/pylint/issues/1498 for None Values previousRule = None for r in ruleStartingLine: if previousRule != None: rules.append(rules_str[previousRule[0]:r[0]]) previousRule = r rules.append(rules_str[previousRule[0]:len(rules_str)-1]) for i, rule in enumerate(rules): current_label = re.search(label_seperator,rule) if current_label: # `labels:` block exists # determine if there are any existing entries entries = re.search(section_seperator,rule[current_label.end():]) if entries: entries_start = current_label.end() entries_end = entries.end()+current_label.end()-section_seperator_len rules[i] = rule[:entries_end] + additional_rule_labels_condition_start + additional_rule_labels + additional_rule_labels_condition_end + rule[entries_end:] else: # `labels:` does not contain any entries # append template to label section rules[i] += additional_rule_labels_condition_start + additional_rule_labels + additional_rule_labels_condition_end else: # `labels:` block does not exist # create it and append template rules[i] += additional_rule_labels_condition_start + "\n" + " " * indent + " labels:" + additional_rule_labels + additional_rule_labels_condition_end return head + "".join(rules) + "\n" def add_custom_annotations(rules, group, indent=4): """Add if wrapper for additional rules annotations""" rule_condition = '{{- if .Values.defaultRules.additionalRuleAnnotations }}\n{{ toYaml .Values.defaultRules.additionalRuleAnnotations | indent 8 }}\n{{- end }}' rule_group_labels = get_rule_group_condition(condition_map.get(group['name'], ''), 'additionalRuleGroupAnnotations') rule_group_condition = '\n{{- if %s }}\n{{ toYaml %s | indent 8 }}\n{{- end }}' % (rule_group_labels, rule_group_labels) annotations = " annotations:" annotations_len = len(annotations) + 1 rule_condition_len = len(rule_condition) + 1 rule_group_condition_len = len(rule_group_condition) separator = " " * indent + "- alert:.*" alerts_positions = re.finditer(separator,rules) alert = 0 for alert_position in alerts_positions: # Add rule_condition after 'annotations:' statement index = alert_position.end() + annotations_len + (rule_condition_len + rule_group_condition_len) * alert rules = rules[:index] + "\n" + rule_condition + rule_group_condition + rules[index:] alert += 1 return rules def add_custom_keep_firing_for(rules, indent=4): """Add if wrapper for additional rules annotations""" indent_spaces = " " * indent + " " keep_firing_for = (indent_spaces + '{{- with .Values.defaultRules.keepFiringFor }}\n' + indent_spaces + 'keep_firing_for: "{{ . }}"\n' + indent_spaces + '{{- end }}') keep_firing_for_len = len(keep_firing_for) + 1 separator = " " * indent + " for:.*" alerts_positions = re.finditer(separator, rules) alert = 0 for alert_position in alerts_positions: # Add rule_condition after 'annotations:' statement index = alert_position.end() + keep_firing_for_len * alert rules = rules[:index] + "\n" + keep_firing_for + rules[index:] alert += 1 return rules def add_custom_for(rules, indent=4): """Add custom 'for:' condition in rules""" replace_field = "for:" rules = add_custom_alert_rules(rules, replace_field, indent) return rules def add_custom_severity(rules, indent=4): """Add custom 'severity:' condition in rules""" replace_field = "severity:" rules = add_custom_alert_rules(rules, replace_field, indent) return rules def add_custom_alert_rules(rules, key_to_replace, indent): """Extend alert field to allow custom values""" key_to_replace_indented = ' ' * indent + key_to_replace alertkey_field = '- alert:' found_alert_key = False alertname = None updated_rules = '' # pylint: disable=C0200 i = 0 while i < len(rules): if rules[i:i + len(alertkey_field)] == alertkey_field: found_alert_key = True start_index_word_after = i + len(alertkey_field) + 1 end_index_alertkey_field = start_index_word_after while end_index_alertkey_field < len(rules) and rules[end_index_alertkey_field].isalnum(): end_index_alertkey_field += 1 alertname = rules[start_index_word_after:end_index_alertkey_field] if found_alert_key: if rules[i:i + len(key_to_replace_indented)] == key_to_replace_indented: found_alert_key = False start_index_key_value = i + len(key_to_replace_indented) + 1 end_index_key_to_replace = start_index_key_value while end_index_key_to_replace < len(rules) and rules[end_index_key_to_replace].isalnum(): end_index_key_to_replace += 1 word_after_key_to_replace = rules[start_index_key_value:end_index_key_to_replace] new_key = key_to_replace_indented + ' {{ dig "' + alertname + \ '" "' + key_to_replace[:-1] + '" "' + \ word_after_key_to_replace + '" .Values.customRules }}' updated_rules += new_key i = end_index_key_to_replace updated_rules += rules[i] i += 1 return updated_rules def write_group_to_file(group, url, destination, min_kubernetes, max_kubernetes): fix_expr(group['rules']) group_name = group['name'] # prepare rules string representation rules = yaml_str_repr(group) # add replacements of custom variables and include their initialisation in case it's needed init_line = '' for line in replacement_map: if group_name in replacement_map[line].get('limitGroup', [group_name]) and line in rules: rules = rules.replace(line, replacement_map[line]['replacement']) if replacement_map[line]['init']: init_line += '\n' + replacement_map[line]['init'] # append per-alert rules rules = add_custom_labels(rules, group) rules = add_custom_annotations(rules, group) rules = add_custom_keep_firing_for(rules) rules = add_custom_for(rules) rules = add_custom_severity(rules) rules = add_rules_conditions_from_condition_map(rules) rules = add_rules_per_rule_conditions(rules, group) # initialize header lines = header % { 'name': sanitize_name(group['name']), 'url': url, 'condition': condition_map.get(group['name'], ''), 'init_line': init_line, 'min_kubernetes': min_kubernetes, 'max_kubernetes': max_kubernetes } # rules themselves lines += re.sub( r'\s(by|on) ?\(', r' \1 ({{ range $.Values.defaultRules.additionalAggregationLabels }}{{ . }},{{ end }}', rules, flags=re.IGNORECASE ) # footer lines += '{{- end }}' filename = group['name'] + '.yaml' new_filename = "%s/%s" % (destination, filename) # make sure directories to store the file exist os.makedirs(destination, exist_ok=True) # recreate the file with open(new_filename, 'w') as f: f.write(lines) print("Generated %s" % new_filename) def write_rules_names_template(): with open('../templates/prometheus/_rules.tpl', 'w') as f: f.write('''{{- /* Generated file. Do not change in-place! In order to change this file first read following link: https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack/hack */ -}}\n''') f.write('{{- define "rules.names" }}\n') f.write('rules:\n') for rule in condition_map: f.write(' - "%s"\n' % sanitize_name(rule)) f.write('{{- end }}') def main(): os.chdir(os.path.dirname(os.path.abspath(__file__))) init_yaml_styles() # read the rules, create a new template file per group for chart in charts: if 'git' in chart: if 'source' not in chart: chart['source'] = '_mixin.jsonnet' url = chart['git'] print("Clone %s" % chart['git']) checkout_dir = os.path.basename(chart['git']) shutil.rmtree(checkout_dir, ignore_errors=True) branch = "main" if 'branch' in chart: branch = chart['branch'] subprocess.run(["git", "init", "--initial-branch", "main", checkout_dir, "--quiet"]) subprocess.run(["git", "-C", checkout_dir, "remote", "add", "origin", chart['git']]) subprocess.run(["git", "-C", checkout_dir, "fetch", "--depth", "1", "origin", branch, "--quiet"]) subprocess.run(["git", "-c", "advice.detachedHead=false", "-C", checkout_dir, "checkout", "FETCH_HEAD", "--quiet"]) if chart.get('mixin'): cwd = os.getcwd() source_cwd = chart['cwd'] mixin_file = chart['source'] mixin_dir = cwd + '/' + checkout_dir + '/' + source_cwd + '/' if os.path.exists(mixin_dir + "jsonnetfile.json"): print("Running jsonnet-bundler, because jsonnetfile.json exists") subprocess.run(["jb", "install"], cwd=mixin_dir) if 'content' in chart: f = open(mixin_dir + mixin_file, "w") f.write(chart['content']) f.close() print("Generating rules from %s" % mixin_file) print("Change cwd to %s" % checkout_dir + '/' + source_cwd) os.chdir(mixin_dir) alerts = json.loads(_jsonnet.evaluate_snippet(mixin_file, chart['mixin'], import_callback=jsonnet_import_callback)) os.chdir(cwd) else: with open(checkout_dir + '/' + chart['source'], "r") as f: raw_text = f.read() alerts = yaml.full_load(raw_text) else: url = chart['source'] print("Generating rules from %s" % url) response = requests.get(url) if response.status_code != 200: print('Skipping the file, response code %s not equals 200' % response.status_code) continue raw_text = response.text if chart.get('mixin'): alerts = json.loads(_jsonnet.evaluate_snippet(url, raw_text + '.prometheusAlerts')) else: alerts = yaml.full_load(raw_text) if ('max_kubernetes' not in chart): chart['max_kubernetes']="9.9.9-9" # etcd workaround, their file don't have spec level groups = alerts['spec']['groups'] if alerts.get('spec') else alerts['groups'] for group in groups: write_group_to_file(group, url, chart['destination'], chart['min_kubernetes'], chart['max_kubernetes']) # write rules.names named template write_rules_names_template() print("Finished") def sanitize_name(name): return re.sub('[_]', '-', name).lower() def jsonnet_import_callback(base, rel): # rel_base is the path relative to the current cwd. # see https://github.com/prometheus-community/helm-charts/issues/5283 # for more details. rel_base = base if rel_base.startswith(os.getcwd()): rel_base = base[len(os.getcwd()):] if "github.com" in rel: base = os.getcwd() + '/vendor/' elif "github.com" in rel_base: base = os.getcwd() + '/vendor/' + rel_base[rel_base.find('github.com'):] if os.path.isfile(base + rel): return base + rel, open(base + rel).read().encode('utf-8') raise RuntimeError('File not found') if __name__ == '__main__': main()