diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c0fc64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bazel* \ No newline at end of file diff --git a/BUILD b/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..d80f088 --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,9 @@ +workspace(name = "io_bazel_rules_prometheus") + +load(":deps.bzl", "prometheus_repositories") + +prometheus_repositories() + +register_toolchains( + "//prometheus/toolchain:all", +) diff --git a/deps.bzl b/deps.bzl new file mode 100644 index 0000000..234ef59 --- /dev/null +++ b/deps.bzl @@ -0,0 +1,15 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +PROMETHEUS_VERSION = "2.23.0" +PROMETHEUS_DARWIN_ARCH = "darwin-amd64" +PROMETHEUS_DARWIN = "{PROMETHEUS_VERSION}.{PROMETHEUS_DARWIN_ARCH}" +PROMETHEUS_DARWIN_URL = "https://github.com/prometheus/prometheus/releases/download/v{PROMETHEUS_VERSION}/prometheus-{PROMETHEUS_VERSION}.{PROMETHEUS_DARWIN_ARCH}.tar.gz" + +def prometheus_repositories(): + http_archive( + name = "prometheus_darwin", + sha256 = "d589a45495cea1aa74bff82335d2145f2d93b8b357c3398739b9139c74dc0cfe", + urls = [PROMETHEUS_DARWIN_URL], + strip_prefix = "prometheus-%s.%s" % (PROMETHEUS_VERSION, PROMETHEUS_DARWIN_ARCH), + build_file = "@//:prometheus.BUILD", + ) diff --git a/examples/BUILD b/examples/BUILD new file mode 100644 index 0000000..fe334e0 --- /dev/null +++ b/examples/BUILD @@ -0,0 +1,59 @@ +load("//prometheus:prometheus.bzl", "promtool_config_test", "promtool_unit_test") + +promtool_unit_test( + name = "test_rules_yml", + srcs = [ + "tests.yml", + ], + rules = ["rules.yml"], +) + +promtool_unit_test( + name = "test_rules_json", + srcs = [ + "tests.json", + ], + rules = ["rules.json"], +) + +promtool_unit_test( + name = "test_rules_multiple", + srcs = [ + "tests.json", + "tests.yml", + ], + rules = [ + "rules.json", + "rules.yml", + ], +) + +promtool_config_test( + name = "test_config_yml", + srcs = ["prometheus.yml"], +) + +promtool_config_test( + name = "test_config_json", + srcs = ["prometheus.json"], +) + +promtool_config_test( + name = "test_config_multiple", + srcs = [ + "prometheus.json", + "prometheus.yml", + ], +) + +test_suite( + name = "all_tests", + tests = [ + ":test_config_json", + ":test_config_multiple", + ":test_config_yml", + ":test_rules_json", + ":test_rules_multiple", + ":test_rules_yml", + ], +) diff --git a/examples/prometheus.json b/examples/prometheus.json new file mode 100644 index 0000000..b97103a --- /dev/null +++ b/examples/prometheus.json @@ -0,0 +1,28 @@ +{ + "global": { + "scrape_interval": "15s", + "evaluation_interval": "15s" + }, + "alerting": { + "alertmanagers": [ + { + "static_configs": [ + { + "targets": null + } + ] + } + ] + }, + "rule_files": null, + "scrape_configs": [ + { + "job_name": "prometheus", + "static_configs": [ + { + "targets": ["localhost:9090"] + } + ] + } + ] +} diff --git a/examples/prometheus.yml b/examples/prometheus.yml new file mode 100644 index 0000000..312b578 --- /dev/null +++ b/examples/prometheus.yml @@ -0,0 +1,29 @@ +# my global config +global: + scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. + evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. + # scrape_timeout is set to the global default (10s). + +# Alertmanager configuration +alerting: + alertmanagers: + - static_configs: + - targets: + # - alertmanager:9093 + +# Load rules once and periodically evaluate them according to the global 'evaluation_interval'. +rule_files: + # - "first_rules.yml" + # - "second_rules.yml" + +# A scrape configuration containing exactly one endpoint to scrape: +# Here it's Prometheus itself. +scrape_configs: + # The job name is added as a label `job=` to any timeseries scraped from this config. + - job_name: "prometheus" + + # metrics_path defaults to '/metrics' + # scheme defaults to 'http'. + + static_configs: + - targets: ["localhost:9090"] diff --git a/examples/rules.json b/examples/rules.json new file mode 100644 index 0000000..dee0bb3 --- /dev/null +++ b/examples/rules.json @@ -0,0 +1,33 @@ +{ + "groups": [ + { + "name": "example", + "rules": [ + { + "alert": "InstanceDown", + "expr": "up == 0", + "for": "5m", + "labels": { + "severity": "page" + }, + "annotations": { + "summary": "Instance {{ $labels.instance }} down", + "description": "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes." + } + }, + { + "alert": "AnotherInstanceDown", + "expr": "up == 0", + "for": "10m", + "labels": { + "severity": "page" + }, + "annotations": { + "summary": "Instance {{ $labels.instance }} down", + "description": "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes." + } + } + ] + } + ] +} diff --git a/examples/rules.yml b/examples/rules.yml new file mode 100644 index 0000000..895ccaa --- /dev/null +++ b/examples/rules.yml @@ -0,0 +1,22 @@ +# This is the rules file. + +groups: + - name: example + rules: + - alert: InstanceDown + expr: up == 0 + for: 5m + labels: + severity: page + annotations: + summary: "Instance {{ $labels.instance }} down" + description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes." + + - alert: AnotherInstanceDown + expr: up == 0 + for: 10m + labels: + severity: page + annotations: + summary: "Instance {{ $labels.instance }} down" + description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 5 minutes." diff --git a/examples/tests.json b/examples/tests.json new file mode 100644 index 0000000..257b292 --- /dev/null +++ b/examples/tests.json @@ -0,0 +1,62 @@ +{ + "rule_files": ["rules.json"], + "evaluation_interval": "1m", + "tests": [ + { + "interval": "1m", + "input_series": [ + { + "series": "up{job=\"prometheus\", instance=\"localhost:9090\"}", + "values": "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0" + }, + { + "series": "up{job=\"node_exporter\", instance=\"localhost:9100\"}", + "values": "1+0x6 0 0 0 0 0 0 0 0" + }, + { + "series": "go_goroutines{job=\"prometheus\", instance=\"localhost:9090\"}", + "values": "10+10x2 30+20x5" + }, + { + "series": "go_goroutines{job=\"node_exporter\", instance=\"localhost:9100\"}", + "values": "10+10x7 10+30x4" + } + ], + "alert_rule_test": [ + { + "eval_time": "10m", + "alertname": "InstanceDown", + "exp_alerts": [ + { + "exp_labels": { + "severity": "page", + "instance": "localhost:9090", + "job": "prometheus" + }, + "exp_annotations": { + "summary": "Instance localhost:9090 down", + "description": "localhost:9090 of job prometheus has been down for more than 5 minutes." + } + } + ] + } + ], + "promql_expr_test": [ + { + "expr": "go_goroutines > 5", + "eval_time": "4m", + "exp_samples": [ + { + "labels": "go_goroutines{job=\"prometheus\",instance=\"localhost:9090\"}", + "value": 50 + }, + { + "labels": "go_goroutines{job=\"node_exporter\",instance=\"localhost:9100\"}", + "value": 50 + } + ] + } + ] + } + ] +} diff --git a/examples/tests.yml b/examples/tests.yml new file mode 100644 index 0000000..6b5b028 --- /dev/null +++ b/examples/tests.yml @@ -0,0 +1,48 @@ +# This is the main input for unit testing. +# Only this file is passed as command line argument. + +rule_files: + - rules.yml + +evaluation_interval: 1m + +tests: + # Test 1. + - interval: 1m + # Series data. + input_series: + - series: 'up{job="prometheus", instance="localhost:9090"}' + values: "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0" + - series: 'up{job="node_exporter", instance="localhost:9100"}' + values: "1+0x6 0 0 0 0 0 0 0 0" # 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 + - series: 'go_goroutines{job="prometheus", instance="localhost:9090"}' + values: "10+10x2 30+20x5" # 10 20 30 30 50 70 90 110 130 + - series: 'go_goroutines{job="node_exporter", instance="localhost:9100"}' + values: "10+10x7 10+30x4" # 10 20 30 40 50 60 70 80 10 40 70 100 130 + + # Unit test for alerting rules. + alert_rule_test: + # Unit test 1. + - eval_time: 10m + alertname: InstanceDown + exp_alerts: + # Alert 1. + - exp_labels: + severity: page + instance: localhost:9090 + job: prometheus + exp_annotations: + summary: "Instance localhost:9090 down" + description: "localhost:9090 of job prometheus has been down for more than 5 minutes." + # Unit tests for promql expressions. + promql_expr_test: + # Unit test 1. + - expr: go_goroutines > 5 + eval_time: 4m + exp_samples: + # Sample 1. + - labels: 'go_goroutines{job="prometheus",instance="localhost:9090"}' + value: 50 + # Sample 2. + - labels: 'go_goroutines{job="node_exporter",instance="localhost:9100"}' + value: 50 diff --git a/prometheus.BUILD b/prometheus.BUILD new file mode 100644 index 0000000..6b1a1b8 --- /dev/null +++ b/prometheus.BUILD @@ -0,0 +1,4 @@ +exports_files([ + "prometheus", + "promtool", +]) diff --git a/prometheus/BUILD b/prometheus/BUILD new file mode 100644 index 0000000..ffd0fb0 --- /dev/null +++ b/prometheus/BUILD @@ -0,0 +1 @@ +package(default_visibility = ["//visibility:public"]) diff --git a/prometheus/internal/BUILD b/prometheus/internal/BUILD new file mode 100644 index 0000000..331ddf1 --- /dev/null +++ b/prometheus/internal/BUILD @@ -0,0 +1,12 @@ +package( + default_visibility = ["//prometheus:__subpackages__"], +) + +exports_files( + glob([ + "*.bat", + "*.sh", + "*.sh.tpl", + ]), + visibility = ["//visibility:public"], +) diff --git a/prometheus/internal/promtool.bzl b/prometheus/internal/promtool.bzl new file mode 100644 index 0000000..7e43606 --- /dev/null +++ b/prometheus/internal/promtool.bzl @@ -0,0 +1,75 @@ +def _promtool_unit_test_impl(ctx): + """promtool_unit_test implementation: we spawn test runner task from template and provide required tools and actions from toolchain""" + + # To ensure the files needed by the script are available, we put them in + # the runfiles. + promtool_info = ctx.toolchains["//prometheus/toolchain:toolchain_type"].prometheusToolchainInfo.promtool + promtool_unit_test_runner_template = promtool_info.template.files.to_list()[0] + + runfiles = ctx.runfiles( + files = ctx.files.srcs + ctx.files.rules, + transitive_files = promtool_info.tool.files, + ) + + test = ctx.actions.declare_file("%s.out.sh" % ctx.label.name) + + ctx.actions.expand_template( + template = promtool_unit_test_runner_template, + output = test, + is_executable = True, + substitutions = { + "%srcs%": " ".join([_file.short_path for _file in ctx.files.srcs]), + "%tool_path%": "%s" % promtool_info.tool.files_to_run.executable.short_path, + "%action%": ctx.attr._action, + }, + ) + return [DefaultInfo(runfiles = runfiles, executable = test)] + +promtool_unit_test = rule( + implementation = _promtool_unit_test_impl, + test = True, + attrs = { + "_action": attr.string(default = "test rules"), + "srcs": attr.label_list(mandatory = True, allow_files = True, cfg = "target"), + "rules": attr.label_list(mandatory = True, allow_files = True), + }, + toolchains = ["//prometheus/toolchain:toolchain_type"], +) + +def _promtool_config_test_impl(ctx): + """promtool_unit_test implementation: we spawn executor task from template and provide required tools""" + + # To ensure the files needed by the script are available, we put them in + # the runfiles. + + promtool_info = ctx.toolchains["//prometheus/toolchain:toolchain_type"].prometheusToolchainInfo.promtool + promtool_unit_test_runner_template = promtool_info.template.files.to_list()[0] + + runfiles = ctx.runfiles( + files = ctx.files.srcs, + transitive_files = promtool_info.tool.files, + ) + + test = ctx.actions.declare_file("%s.out.sh" % ctx.label.name) + + ctx.actions.expand_template( + template = promtool_unit_test_runner_template, + output = test, + is_executable = True, + substitutions = { + "%srcs%": " ".join([_file.short_path for _file in ctx.files.srcs]), + "%tool_path%": "%s" % promtool_info.tool.files_to_run.executable.short_path, + "%action%": ctx.attr._action, + }, + ) + return [DefaultInfo(runfiles = runfiles, executable = test)] + +promtool_config_test = rule( + implementation = _promtool_config_test_impl, + test = True, + attrs = { + "_action": attr.string(default = "check config"), + "srcs": attr.label_list(mandatory = True, allow_files = True), + }, + toolchains = ["//prometheus/toolchain:toolchain_type"], +) diff --git a/prometheus/internal/promtool.sh.tpl b/prometheus/internal/promtool.sh.tpl new file mode 100755 index 0000000..8fa7316 --- /dev/null +++ b/prometheus/internal/promtool.sh.tpl @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -e + +%tool_path% %action% %srcs% diff --git a/prometheus/internal/providers.bzl b/prometheus/internal/providers.bzl new file mode 100644 index 0000000..3bd3937 --- /dev/null +++ b/prometheus/internal/providers.bzl @@ -0,0 +1,8 @@ +PromtoolInfo = provider(fields = { + "tool": "Promtool label", + "template": "Template script that will be filled with execution details", +}) + +PrometheusInfo = provider(fields = { + "tool": "Prometheus label", +}) diff --git a/prometheus/prometheus.bzl b/prometheus/prometheus.bzl new file mode 100644 index 0000000..6fb89de --- /dev/null +++ b/prometheus/prometheus.bzl @@ -0,0 +1,11 @@ +load( + "//prometheus/internal:promtool.bzl", + _promtool_unit_test = "promtool_unit_test", +) +load( + "//prometheus/internal:promtool.bzl", + _promtool_config_test = "promtool_config_test", +) + +promtool_unit_test = _promtool_unit_test +promtool_config_test = _promtool_config_test diff --git a/prometheus/toolchain/BUILD b/prometheus/toolchain/BUILD new file mode 100644 index 0000000..848ca0e --- /dev/null +++ b/prometheus/toolchain/BUILD @@ -0,0 +1,28 @@ +package( + default_visibility = ["//visibility:public"], +) + +load(":toolchain.bzl", "prometheus_toolchain") + +toolchain_type(name = "toolchain_type") + +prometheus_toolchain( + name = "prometheus_darwin", + prometheus = "@prometheus_darwin//:prometheus", + promtool = "@prometheus_darwin//:promtool", + promtool_executor_template = "//prometheus/internal:promtool.sh.tpl", +) + +toolchain( + name = "prometheus_toolchain_darwin", + exec_compatible_with = [ + "@platforms//os:osx", + "@platforms//cpu:x86_64", + ], + target_compatible_with = [ + "@platforms//os:osx", + "@platforms//cpu:x86_64", + ], + toolchain = ":prometheus_darwin", + toolchain_type = ":toolchain_type", +) diff --git a/prometheus/toolchain/toolchain.bzl b/prometheus/toolchain/toolchain.bzl new file mode 100644 index 0000000..def514c --- /dev/null +++ b/prometheus/toolchain/toolchain.bzl @@ -0,0 +1,36 @@ +load("//prometheus/internal:providers.bzl", "PrometheusInfo", "PromtoolInfo") + +PrometheusToolchainInfo = provider(fields = [ + "name", + "prometheus", + "promtool", +]) + +# genrule to work with real machine instead of sandboxed bazel environment? + +def _prometheus_toolchain_impl(ctx): + toolchain_info = platform_common.ToolchainInfo( + prometheusToolchainInfo = PrometheusToolchainInfo( + name = ctx.label.name, + prometheus = PrometheusInfo( + tool = ctx.attr.prometheus, + ), + promtool = PromtoolInfo( + tool = ctx.attr.promtool, + template = ctx.attr.promtool_executor_template, + ), + ), + ) + return [ + toolchain_info, + ] + +prometheus_toolchain = rule( + implementation = _prometheus_toolchain_impl, + attrs = { + "prometheus": attr.label(mandatory = True, allow_single_file = True, executable = True, cfg = "exec"), + "promtool": attr.label(mandatory = True, allow_single_file = True, executable = True, cfg = "exec"), + "promtool_executor_template": attr.label(mandatory = True, allow_single_file = True), + }, + provides = [platform_common.ToolchainInfo], +)