diff --git a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml index 808f1b5a6829..0c5ca3cae2e1 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml @@ -844,6 +844,15 @@ option. + + + The services.grafana.provision.alerting + option was added. It includes suboptions for every + alerting-related objects (with the exception of + notifiers), which means it’s now possible + to configure modern Grafana alerting declaratively. + + Matrix Synapse now requires entries in the diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md index 447fe19878e9..29b6b128d0cd 100644 --- a/nixos/doc/manual/release-notes/rl-2211.section.md +++ b/nixos/doc/manual/release-notes/rl-2211.section.md @@ -274,6 +274,8 @@ Available as [services.patroni](options.html#opt-services.patroni.enable). - The `services.grafana.provision.datasources` and `services.grafana.provision.dashboards` options were converted to a [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md) configuration. They also now support specifying the provisioning YAML file with `path` option. +- The `services.grafana.provision.alerting` option was added. It includes suboptions for every alerting-related objects (with the exception of `notifiers`), which means it's now possible to configure modern Grafana alerting declaratively. + - Matrix Synapse now requires entries in the `state_group_edges` table to be unique, in order to prevent accidentally introducing duplicate information (for example, because a database backup was restored multiple times). If your Synapse database already has duplicate rows in this table, this could fail with an error and require manual remediation. - The `diamond` package has been update from 0.8.36 to 2.0.15. See the [upstream release notes](https://github.com/bbuchfink/diamond/releases) for more details. diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index f8af84c40855..c3b0eb005500 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -96,11 +96,25 @@ let notifierFile = pkgs.writeText "notifier.yaml" (builtins.toJSON notifierConfiguration); + generateAlertingProvisioningYaml = x: if (cfg.provision.alerting."${x}".path == null) + then provisioningSettingsFormat.generate "${x}.yaml" cfg.provision.alerting."${x}".settings + else cfg.provision.alerting."${x}".path; + rulesFile = generateAlertingProvisioningYaml "rules"; + contactPointsFile = generateAlertingProvisioningYaml "contactPoints"; + policiesFile = generateAlertingProvisioningYaml "policies"; + templatesFile = generateAlertingProvisioningYaml "templates"; + muteTimingsFile = generateAlertingProvisioningYaml "muteTimings"; + provisionConfDir = pkgs.runCommand "grafana-provisioning" { } '' - mkdir -p $out/{datasources,dashboards,notifiers} + mkdir -p $out/{datasources,dashboards,notifiers,alerting} ln -sf ${datasourceFile} $out/datasources/datasource.yaml ln -sf ${dashboardFile} $out/dashboards/dashboard.yaml ln -sf ${notifierFile} $out/notifiers/notifier.yaml + ln -sf ${rulesFile} $out/alerting/rules.yaml + ln -sf ${contactPointsFile} $out/alerting/contactPoints.yaml + ln -sf ${policiesFile} $out/alerting/policies.yaml + ln -sf ${templatesFile} $out/alerting/templates.yaml + ln -sf ${muteTimingsFile} $out/alerting/muteTimings.yaml ''; # Get a submodule without any embedded metadata: @@ -544,6 +558,461 @@ in { type = types.listOf grafanaTypes.notifierConfig; apply = x: map _filter x; }; + + + alerting = { + rules = { + path = mkOption { + description = lib.mdDoc '' + Path to YAML rules configuration. Can't be used with + `services.grafana.provision.alerting.rules.settings` simultaneously. + ''; + default = null; + type = types.nullOr types.path; + }; + + settings = mkOption { + description = lib.mdDoc '' + Grafana rules configuration in Nix. Can't be used with + `services.grafana.provision.alerting.rules.path` simultaneously. See + + for supported options. + ''; + default = null; + type = types.nullOr (types.submodule { + options = { + apiVersion = mkOption { + description = lib.mdDoc "Config file version."; + default = 1; + type = types.int; + }; + + groups = mkOption { + description = lib.mdDoc "List of rule groups to import or update."; + default = []; + type = types.listOf (types.submodule { + freeformType = provisioningSettingsFormat.type; + + options.name = mkOption { + description = lib.mdDoc "Name of the rule group. Required."; + type = types.str; + }; + + options.folder = mkOption { + description = lib.mdDoc "Name of the folder the rule group will be stored in. Required."; + type = types.str; + }; + + options.interval = mkOption { + description = lib.mdDoc "Interval that the rule group should be evaluated at. Required."; + type = types.str; + }; + }); + }; + + deleteRules = mkOption { + description = lib.mdDoc "List of alert rule UIDs that should be deleted."; + default = []; + type = types.listOf (types.submodule { + options.orgId = mkOption { + description = lib.mdDoc "Organization ID, default = 1"; + default = 1; + type = types.int; + }; + + options.uid = mkOption { + description = lib.mdDoc "Unique identifier for the rule. Required."; + type = types.str; + }; + }); + }; + }; + }); + example = literalExpression '' + { + apiVersion = 1; + + groups = [{ + orgId = 1; + name = "my_rule_group"; + folder = "my_first_folder"; + interval = "60s"; + rules = [{ + uid = "my_id_1"; + title = "my_first_rule"; + condition = "A"; + data = [{ + refId = "A"; + datasourceUid = "-100"; + model = { + conditions = [{ + evaluator = { + params = [ 3 ]; + type = "git"; + }; + operator.type = "and"; + query.params = [ "A" ]; + reducer.type = "last"; + type = "query"; + }]; + datasource = { + type = "__expr__"; + uid = "-100"; + }; + expression = "1==0"; + intervalMs = 1000; + maxDataPoints = 43200; + refId = "A"; + type = "math"; + }; + }]; + dashboardUid = "my_dashboard"; + panelId = 123; + noDataState = "Alerting"; + for = "60s"; + annotations.some_key = "some_value"; + labels.team = "sre_team1"; + }]; + }]; + + deleteRules = [{ + orgId = 1; + uid = "my_id_1"; + }]; + } + ''; + }; + }; + + contactPoints = { + path = mkOption { + description = lib.mdDoc '' + Path to YAML contact points configuration. Can't be used with + `services.grafana.provision.alerting.contactPoints.settings` simultaneously. + ''; + default = null; + type = types.nullOr types.path; + }; + + settings = mkOption { + description = lib.mdDoc '' + Grafana contact points configuration in Nix. Can't be used with + `services.grafana.provision.alerting.contactPoints.path` simultaneously. See + + for supported options. + ''; + default = null; + type = types.nullOr (types.submodule { + options = { + apiVersion = mkOption { + description = lib.mdDoc "Config file version."; + default = 1; + type = types.int; + }; + + contactPoints = mkOption { + description = lib.mdDoc "List of contact points to import or update."; + default = []; + type = types.listOf (types.submodule { + freeformType = provisioningSettingsFormat.type; + + options.name = mkOption { + description = lib.mdDoc "Name of the contact point. Required."; + type = types.str; + }; + }); + }; + + deleteContactPoints = mkOption { + description = lib.mdDoc "List of receivers that should be deleted."; + default = []; + type = types.listOf (types.submodule { + options.orgId = mkOption { + description = lib.mdDoc "Organization ID, default = 1."; + default = 1; + type = types.int; + }; + + options.uid = mkOption { + description = lib.mdDoc "Unique identifier for the receiver. Required."; + type = types.str; + }; + }); + }; + }; + }); + example = literalExpression '' + { + apiVersion = 1; + + contactPoints = [{ + orgId = 1; + name = "cp_1"; + receivers = [{ + uid = "first_uid"; + type = "prometheus-alertmanager"; + settings.url = "http://test:9000"; + }]; + }]; + + deleteContactPoints = [{ + orgId = 1; + uid = "first_uid"; + }]; + } + ''; + }; + }; + + policies = { + path = mkOption { + description = lib.mdDoc '' + Path to YAML notification policies configuration. Can't be used with + `services.grafana.provision.alerting.policies.settings` simultaneously. + ''; + default = null; + type = types.nullOr types.path; + }; + + settings = mkOption { + description = lib.mdDoc '' + Grafana notification policies configuration in Nix. Can't be used with + `services.grafana.provision.alerting.policies.path` simultaneously. See + + for supported options. + ''; + default = null; + type = types.nullOr (types.submodule { + options = { + apiVersion = mkOption { + description = lib.mdDoc "Config file version."; + default = 1; + type = types.int; + }; + + policies = mkOption { + description = lib.mdDoc "List of contact points to import or update."; + default = []; + type = types.listOf (types.submodule { + freeformType = provisioningSettingsFormat.type; + }); + }; + + resetPolicies = mkOption { + description = lib.mdDoc "List of orgIds that should be reset to the default policy."; + default = []; + type = types.listOf types.int; + }; + }; + }); + example = literalExpression '' + { + apiVersion = 1; + + policies = [{ + orgId = 1; + receiver = "grafana-default-email"; + group_by = [ "..." ]; + matchers = [ + "alertname = Watchdog" + "severity =~ \"warning|critical\"" + ]; + mute_time_intervals = [ + "abc" + ]; + group_wait = "30s"; + group_interval = "5m"; + repeat_interval = "4h"; + }]; + + resetPolicies = [ + 1 + ]; + } + ''; + }; + }; + + templates = { + path = mkOption { + description = lib.mdDoc '' + Path to YAML templates configuration. Can't be used with + `services.grafana.provision.alerting.templates.settings` simultaneously. + ''; + default = null; + type = types.nullOr types.path; + }; + + settings = mkOption { + description = lib.mdDoc '' + Grafana templates configuration in Nix. Can't be used with + `services.grafana.provision.alerting.templates.path` simultaneously. See + + for supported options. + ''; + default = null; + type = types.nullOr (types.submodule { + options = { + apiVersion = mkOption { + description = lib.mdDoc "Config file version."; + default = 1; + type = types.int; + }; + + templates = mkOption { + description = lib.mdDoc "List of templates to import or update."; + default = []; + type = types.listOf (types.submodule { + freeformType = provisioningSettingsFormat.type; + + options.name = mkOption { + description = lib.mdDoc "Name of the template, must be unique. Required."; + type = types.str; + }; + + options.template = mkOption { + description = lib.mdDoc "Alerting with a custom text template"; + type = types.str; + }; + }); + }; + + deleteTemplates = mkOption { + description = lib.mdDoc "List of alert rule UIDs that should be deleted."; + default = []; + type = types.listOf (types.submodule { + options.orgId = mkOption { + description = lib.mdDoc "Organization ID, default = 1."; + default = 1; + type = types.int; + }; + + options.name = mkOption { + description = lib.mdDoc "Name of the template, must be unique. Required."; + type = types.str; + }; + }); + }; + }; + }); + example = literalExpression '' + { + apiVersion = 1; + + templates = [{ + orgId = 1; + name = "my_first_template"; + template = "Alerting with a custom text template"; + }]; + + deleteTemplates = [{ + orgId = 1; + name = "my_first_template"; + }]; + } + ''; + }; + }; + + muteTimings = { + path = mkOption { + description = lib.mdDoc '' + Path to YAML mute timings configuration. Can't be used with + `services.grafana.provision.alerting.muteTimings.settings` simultaneously. + ''; + default = null; + type = types.nullOr types.path; + }; + + settings = mkOption { + description = lib.mdDoc '' + Grafana mute timings configuration in Nix. Can't be used with + `services.grafana.provision.alerting.muteTimings.path` simultaneously. See + + for supported options. + ''; + default = null; + type = types.nullOr (types.submodule { + options = { + apiVersion = mkOption { + description = lib.mdDoc "Config file version."; + default = 1; + type = types.int; + }; + + muteTimes = mkOption { + description = lib.mdDoc "List of mute time intervals to import or update."; + default = []; + type = types.listOf (types.submodule { + freeformType = provisioningSettingsFormat.type; + + options.name = mkOption { + description = lib.mdDoc "Name of the mute time interval, must be unique. Required."; + type = types.str; + }; + }); + }; + + deleteMuteTimes = mkOption { + description = lib.mdDoc "List of mute time intervals that should be deleted."; + default = []; + type = types.listOf (types.submodule { + options.orgId = mkOption { + description = lib.mdDoc "Organization ID, default = 1."; + default = 1; + type = types.int; + }; + + options.name = mkOption { + description = lib.mdDoc "Name of the mute time interval, must be unique. Required."; + type = types.str; + }; + }); + }; + }; + }); + example = literalExpression '' + { + apiVersion = 1; + + muteTimes = [{ + orgId = 1; + name = "mti_1"; + time_intervals = [{ + times = [{ + start_time = "06:00"; + end_time = "23:59"; + }]; + weekdays = [ + "monday:wednesday" + "saturday" + "sunday" + ]; + months = [ + "1:3" + "may:august" + "december" + ]; + years = [ + "2020:2022" + "2030" + ]; + days_of_month = [ + "1:5" + "-3:-1" + ]; + }]; + }]; + + deleteMuteTimes = [{ + orgId = 1; + name = "mti_1"; + }]; + } + ''; + }; + }; + }; }; security = { @@ -841,6 +1310,26 @@ in { assertion = if (builtins.isList cfg.provision.dashboards) then true else cfg.provision.dashboards.settings == null || cfg.provision.dashboards.path == null; message = "Cannot set both dashboards settings and dashboards path"; } + { + assertion = cfg.provision.alerting.rules.settings == null || cfg.provision.alerting.rules.path == null; + message = "Cannot set both rules settings and rules path"; + } + { + assertion = cfg.provision.alerting.contactPoints.settings == null || cfg.provision.alerting.contactPoints.path == null; + message = "Cannot set both contact points settings and contact points path"; + } + { + assertion = cfg.provision.alerting.policies.settings == null || cfg.provision.alerting.policies.path == null; + message = "Cannot set both policies settings and policies path"; + } + { + assertion = cfg.provision.alerting.templates.settings == null || cfg.provision.alerting.templates.path == null; + message = "Cannot set both templates settings and templates path"; + } + { + assertion = cfg.provision.alerting.muteTimings.settings == null || cfg.provision.alerting.muteTimings.path == null; + message = "Cannot set both mute timings settings and mute timings path"; + } ]; systemd.services.grafana = { diff --git a/nixos/tests/grafana/default.nix b/nixos/tests/grafana/default.nix index 9b299cc6aa5c..d08cbb67ce85 100644 --- a/nixos/tests/grafana/default.nix +++ b/nixos/tests/grafana/default.nix @@ -8,4 +8,9 @@ provision-datasources = import ./provision-datasources { inherit system pkgs; }; provision-dashboards = import ./provision-dashboards { inherit system pkgs; }; provision-notifiers = import ./provision-notifiers.nix { inherit system pkgs; }; + provision-rules = import ./provision-rules { inherit system pkgs; }; + provision-contact-points = import ./provision-contact-points { inherit system pkgs; }; + provision-policies = import ./provision-policies { inherit system pkgs; }; + provision-templates = import ./provision-templates { inherit system pkgs; }; + provision-mute-timings = import ./provision-mute-timings { inherit system pkgs; }; } diff --git a/nixos/tests/grafana/provision-contact-points/default.nix b/nixos/tests/grafana/provision-contact-points/default.nix new file mode 100644 index 000000000000..4e2bbfa91a68 --- /dev/null +++ b/nixos/tests/grafana/provision-contact-points/default.nix @@ -0,0 +1,76 @@ +args@{ pkgs, ... }: + +(import ../../make-test-python.nix ({ lib, pkgs, ... }: + +let + inherit (lib) mkMerge nameValuePair maintainers; + + baseGrafanaConf = { + services.grafana = { + enable = true; + addr = "localhost"; + analytics.reporting.enable = false; + domain = "localhost"; + security = { + adminUser = "testadmin"; + adminPassword = "snakeoilpwd"; + }; + provision.enable = true; + }; + }; + + extraNodeConfs = { + provisionContactPointsNix = { + services.grafana.provision = { + alerting.contactPoints.settings = { + contactPoints = [{ + name = "Test Contact Point"; + receivers = [{ + uid = "test_contact_point"; + type = "prometheus-alertmanager"; + settings.url = "http://localhost:9000"; + }]; + }]; + }; + }; + }; + + provisionContactPointsYaml = { + services.grafana.provision.alerting.contactPoints.path = ./provision-contact-points.yaml; + }; + }; + + nodes = builtins.listToAttrs (map (provisionType: + nameValuePair provisionType (mkMerge [ + baseGrafanaConf + (extraNodeConfs.${provisionType} or {}) + ])) [ "provisionContactPointsNix" "provisionContactPointsYaml" ]); + +in { + name = "grafana-provision-contact-points"; + + meta = with maintainers; { + maintainers = [ kfears willibutz ]; + }; + + inherit nodes; + + testScript = '' + start_all() + with subtest("Successful contact point provision with Nix"): + provisionContactPointsNix.wait_for_unit("grafana.service") + provisionContactPointsNix.wait_for_open_port(3000) + provisionContactPointsNix.succeed( + "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/contact-points | grep Test\ Contact\ Point" + ) + provisionContactPointsNix.shutdown() + + with subtest("Successful contact point provision with YAML"): + provisionContactPointsYaml.wait_for_unit("grafana.service") + provisionContactPointsYaml.wait_for_open_port(3000) + provisionContactPointsYaml.succeed( + "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/contact-points | grep Test\ Contact\ Point" + ) + provisionContactPointsYaml.shutdown() + ''; +})) args diff --git a/nixos/tests/grafana/provision-contact-points/provision-contact-points.yaml b/nixos/tests/grafana/provision-contact-points/provision-contact-points.yaml new file mode 100644 index 000000000000..2a5f14e75e2d --- /dev/null +++ b/nixos/tests/grafana/provision-contact-points/provision-contact-points.yaml @@ -0,0 +1,9 @@ +apiVersion: 1 + +contactPoints: + - name: "Test Contact Point" + receivers: + - uid: "test_contact_point" + type: prometheus-alertmanager + settings: + url: http://localhost:9000 diff --git a/nixos/tests/grafana/provision-mute-timings/default.nix b/nixos/tests/grafana/provision-mute-timings/default.nix new file mode 100644 index 000000000000..969dd7138b91 --- /dev/null +++ b/nixos/tests/grafana/provision-mute-timings/default.nix @@ -0,0 +1,71 @@ +args@{ pkgs, ... }: + +(import ../../make-test-python.nix ({ lib, pkgs, ... }: + +let + inherit (lib) mkMerge nameValuePair maintainers; + + baseGrafanaConf = { + services.grafana = { + enable = true; + addr = "localhost"; + analytics.reporting.enable = false; + domain = "localhost"; + security = { + adminUser = "testadmin"; + adminPassword = "snakeoilpwd"; + }; + provision.enable = true; + }; + }; + + extraNodeConfs = { + provisionMuteTimingsNix = { + services.grafana.provision = { + alerting.muteTimings.settings = { + muteTimes = [{ + name = "Test Mute Timing"; + }]; + }; + }; + }; + + provisionMuteTimingsYaml = { + services.grafana.provision.alerting.muteTimings.path = ./provision-mute-timings.yaml; + }; + }; + + nodes = builtins.listToAttrs (map (provisionType: + nameValuePair provisionType (mkMerge [ + baseGrafanaConf + (extraNodeConfs.${provisionType} or {}) + ])) [ "provisionMuteTimingsNix" "provisionMuteTimingsYaml" ]); + +in { + name = "grafana-provision-mute-timings"; + + meta = with maintainers; { + maintainers = [ kfears willibutz ]; + }; + + inherit nodes; + + testScript = '' + start_all() + with subtest("Successful mute timings provision with Nix"): + provisionMuteTimingsNix.wait_for_unit("grafana.service") + provisionMuteTimingsNix.wait_for_open_port(3000) + provisionMuteTimingsNix.succeed( + "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/mute-timings | grep Test\ Mute\ Timing" + ) + provisionMuteTimingsNix.shutdown() + + with subtest("Successful mute timings provision with YAML"): + provisionMuteTimingsYaml.wait_for_unit("grafana.service") + provisionMuteTimingsYaml.wait_for_open_port(3000) + provisionMuteTimingsYaml.succeed( + "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/mute-timings | grep Test\ Mute\ Timing" + ) + provisionMuteTimingsYaml.shutdown() + ''; +})) args diff --git a/nixos/tests/grafana/provision-mute-timings/provision-mute-timings.yaml b/nixos/tests/grafana/provision-mute-timings/provision-mute-timings.yaml new file mode 100644 index 000000000000..1f47f7c18f0c --- /dev/null +++ b/nixos/tests/grafana/provision-mute-timings/provision-mute-timings.yaml @@ -0,0 +1,4 @@ +apiVersion: 1 + +muteTimes: + - name: "Test Mute Timing" diff --git a/nixos/tests/grafana/provision-policies/default.nix b/nixos/tests/grafana/provision-policies/default.nix new file mode 100644 index 000000000000..1dff6936e76c --- /dev/null +++ b/nixos/tests/grafana/provision-policies/default.nix @@ -0,0 +1,82 @@ +args@{ pkgs, ... }: + +(import ../../make-test-python.nix ({ lib, pkgs, ... }: + +let + inherit (lib) mkMerge nameValuePair maintainers; + + baseGrafanaConf = { + services.grafana = { + enable = true; + addr = "localhost"; + analytics.reporting.enable = false; + domain = "localhost"; + security = { + adminUser = "testadmin"; + adminPassword = "snakeoilpwd"; + }; + provision.enable = true; + }; + }; + + extraNodeConfs = { + provisionPoliciesNix = { + services.grafana.provision = { + alerting.policies.settings = { + policies = [{ + receiver = "Test Contact Point"; + }]; + }; + alerting.contactPoints.settings = { + contactPoints = [{ + name = "Test Contact Point"; + receivers = [{ + uid = "test_contact_point"; + type = "prometheus-alertmanager"; + settings.url = "http://localhost:9000"; + }]; + }]; + }; + }; + }; + + provisionPoliciesYaml = { + services.grafana.provision.alerting.policies.path = ./provision-policies.yaml; + services.grafana.provision.alerting.contactPoints.path = ./provision-contact-points.yaml; + }; + }; + + nodes = builtins.listToAttrs (map (provisionType: + nameValuePair provisionType (mkMerge [ + baseGrafanaConf + (extraNodeConfs.${provisionType} or {}) + ])) [ "provisionPoliciesNix" "provisionPoliciesYaml" ]); + +in { + name = "grafana-provision-policies"; + + meta = with maintainers; { + maintainers = [ kfears willibutz ]; + }; + + inherit nodes; + + testScript = '' + start_all() + with subtest("Successful policy provision with Nix"): + provisionPoliciesNix.wait_for_unit("grafana.service") + provisionPoliciesNix.wait_for_open_port(3000) + provisionPoliciesNix.succeed( + "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/contact-points | grep Test\ Contact\ Point" + ) + provisionPoliciesNix.shutdown() + + with subtest("Successful policy provision with YAML"): + provisionPoliciesYaml.wait_for_unit("grafana.service") + provisionPoliciesYaml.wait_for_open_port(3000) + provisionPoliciesYaml.succeed( + "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/contact-points | grep Test\ Contact\ Point" + ) + provisionPoliciesYaml.shutdown() + ''; +})) args diff --git a/nixos/tests/grafana/provision-policies/provision-contact-points.yaml b/nixos/tests/grafana/provision-policies/provision-contact-points.yaml new file mode 100644 index 000000000000..2a5f14e75e2d --- /dev/null +++ b/nixos/tests/grafana/provision-policies/provision-contact-points.yaml @@ -0,0 +1,9 @@ +apiVersion: 1 + +contactPoints: + - name: "Test Contact Point" + receivers: + - uid: "test_contact_point" + type: prometheus-alertmanager + settings: + url: http://localhost:9000 diff --git a/nixos/tests/grafana/provision-policies/provision-policies.yaml b/nixos/tests/grafana/provision-policies/provision-policies.yaml new file mode 100644 index 000000000000..eb31126c4ba5 --- /dev/null +++ b/nixos/tests/grafana/provision-policies/provision-policies.yaml @@ -0,0 +1,4 @@ +apiVersion: 1 + +policies: + - receiver: "Test Contact Point" diff --git a/nixos/tests/grafana/provision-rules/default.nix b/nixos/tests/grafana/provision-rules/default.nix new file mode 100644 index 000000000000..63b716c5770d --- /dev/null +++ b/nixos/tests/grafana/provision-rules/default.nix @@ -0,0 +1,104 @@ +args@{ pkgs, ... }: + +(import ../../make-test-python.nix ({ lib, pkgs, ... }: + +let + inherit (lib) mkMerge nameValuePair maintainers; + + baseGrafanaConf = { + services.grafana = { + enable = true; + addr = "localhost"; + analytics.reporting.enable = false; + domain = "localhost"; + security = { + adminUser = "testadmin"; + adminPassword = "snakeoilpwd"; + }; + provision.enable = true; + }; + }; + + extraNodeConfs = { + provisionRulesNix = { + services.grafana.provision = { + alerting.rules.settings = { + groups = [{ + name = "test_rule_group"; + folder = "test_folder"; + interval = "60s"; + rules = [{ + uid = "test_rule"; + title = "Test Rule"; + condition = "A"; + data = [{ + refId = "A"; + datasourceUid = "-100"; + model = { + conditions = [{ + evaluator = { + params = [ 3 ]; + type = "git"; + }; + operator.type = "and"; + query.params = [ "A" ]; + reducer.type = "last"; + type = "query"; + }]; + datasource = { + type = "__expr__"; + uid = "-100"; + }; + expression = "1==0"; + intervalMs = 1000; + maxDataPoints = 43200; + refId = "A"; + type = "math"; + }; + }]; + for = "60s"; + }]; + }]; + }; + }; + }; + + provisionRulesYaml = { + services.grafana.provision.alerting.rules.path = ./provision-rules.yaml; + }; + }; + + nodes = builtins.listToAttrs (map (provisionType: + nameValuePair provisionType (mkMerge [ + baseGrafanaConf + (extraNodeConfs.${provisionType} or {}) + ])) [ "provisionRulesNix" "provisionRulesYaml" ]); + +in { + name = "grafana-provision-rules"; + + meta = with maintainers; { + maintainers = [ kfears willibutz ]; + }; + + inherit nodes; + + testScript = '' + start_all() + with subtest("Successful rule provision with Nix"): + provisionRulesNix.wait_for_unit("grafana.service") + provisionRulesNix.wait_for_open_port(3000) + provisionRulesNix.succeed( + "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/alert-rules/test_rule | grep Test\ Rule" + ) + provisionRulesNix.shutdown() + + with subtest("Successful rule provision with YAML"): + provisionRulesYaml.wait_for_unit("grafana.service") + provisionRulesYaml.wait_for_open_port(3000) + provisionRulesYaml.succeed( + "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/alert-rules/test_rule | grep Test\ Rule" + ) + provisionRulesYaml.shutdown() + ''; +})) args diff --git a/nixos/tests/grafana/provision-rules/provision-rules.yaml b/nixos/tests/grafana/provision-rules/provision-rules.yaml new file mode 100644 index 000000000000..946539c8cb69 --- /dev/null +++ b/nixos/tests/grafana/provision-rules/provision-rules.yaml @@ -0,0 +1,36 @@ +apiVersion: 1 + +groups: + - name: "test_rule_group" + folder: "test_group" + interval: 60s + rules: + - uid: "test_rule" + title: "Test Rule" + condition: A + data: + - refId: A + datasourceUid: '-100' + model: + conditions: + - evaluator: + params: + - 3 + type: gt + operator: + type: and + query: + params: + - A + reducer: + type: last + type: query + datasource: + type: __expr__ + uid: '-100' + expression: 1==0 + intervalMs: 1000 + maxDataPoints: 43200 + refId: A + type: math + for: 60s diff --git a/nixos/tests/grafana/provision-templates/default.nix b/nixos/tests/grafana/provision-templates/default.nix new file mode 100644 index 000000000000..82ca959e03f7 --- /dev/null +++ b/nixos/tests/grafana/provision-templates/default.nix @@ -0,0 +1,72 @@ +args@{ pkgs, ... }: + +(import ../../make-test-python.nix ({ lib, pkgs, ... }: + +let + inherit (lib) mkMerge nameValuePair maintainers; + + baseGrafanaConf = { + services.grafana = { + enable = true; + addr = "localhost"; + analytics.reporting.enable = false; + domain = "localhost"; + security = { + adminUser = "testadmin"; + adminPassword = "snakeoilpwd"; + }; + provision.enable = true; + }; + }; + + extraNodeConfs = { + provisionTemplatesNix = { + services.grafana.provision = { + alerting.templates.settings = { + templates = [{ + name = "Test Template"; + template = "Test message"; + }]; + }; + }; + }; + + provisionTemplatesYaml = { + services.grafana.provision.alerting.templates.path = ./provision-templates.yaml; + }; + }; + + nodes = builtins.listToAttrs (map (provisionType: + nameValuePair provisionType (mkMerge [ + baseGrafanaConf + (extraNodeConfs.${provisionType} or {}) + ])) [ "provisionTemplatesNix" "provisionTemplatesYaml" ]); + +in { + name = "grafana-provision-rules"; + + meta = with maintainers; { + maintainers = [ kfears willibutz ]; + }; + + inherit nodes; + + testScript = '' + start_all() + with subtest("Successful template provision with Nix"): + provisionTemplatesNix.wait_for_unit("grafana.service") + provisionTemplatesNix.wait_for_open_port(3000) + provisionTemplatesNix.succeed( + "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/templates | grep Test\ Template" + ) + provisionTemplatesNix.shutdown() + + with subtest("Successful template provision with YAML"): + provisionTemplatesYaml.wait_for_unit("grafana.service") + provisionTemplatesYaml.wait_for_open_port(3000) + provisionTemplatesYaml.succeed( + "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/templates | grep Test\ Template" + ) + provisionTemplatesYaml.shutdown() + ''; +})) args diff --git a/nixos/tests/grafana/provision-templates/provision-templates.yaml b/nixos/tests/grafana/provision-templates/provision-templates.yaml new file mode 100644 index 000000000000..09df247b3451 --- /dev/null +++ b/nixos/tests/grafana/provision-templates/provision-templates.yaml @@ -0,0 +1,5 @@ +apiVersion: 1 + +templates: + - name: "Test Template" + template: "Test message"