Merge pull request #199150 from Ma27/grafana-fixup

nixos/grafana: documentation/warning improvements after #191768

authored by Maximilian Bosch and committed by GitHub 853d0a3f bd3a8c7b

+401 -190
+138 -14
nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
··· 1206 1206 </listitem> 1207 1207 <listitem> 1208 1208 <para> 1209 - The <literal>services.grafana</literal> options were converted 1210 - to a 1209 + The module <literal>services.grafana</literal> was refactored 1210 + to be compliant with 1211 1211 <link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">RFC 1212 - 0042</link> configuration. 1212 + 0042</link>. To be precise, this means that the following 1213 + things have changed: 1213 1214 </para> 1214 - </listitem> 1215 - <listitem> 1216 - <para> 1217 - The <literal>services.grafana.provision.datasources</literal> 1218 - and <literal>services.grafana.provision.dashboards</literal> 1219 - options were converted to a 1220 - <link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">RFC 1221 - 0042</link> configuration. They also now support specifying 1222 - the provisioning YAML file with <literal>path</literal> 1223 - option. 1224 - </para> 1215 + <itemizedlist> 1216 + <listitem> 1217 + <para> 1218 + The newly introduced option 1219 + <xref linkend="opt-services.grafana.settings" /> is an 1220 + attribute-set that will be converted into Grafana’s INI 1221 + format. This means that the configuration from 1222 + <link xlink:href="https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/">Grafana’s 1223 + configuration reference</link> can be directly written as 1224 + attribute-set in Nix within this option. 1225 + </para> 1226 + </listitem> 1227 + <listitem> 1228 + <para> 1229 + The option 1230 + <literal>services.grafana.extraOptions</literal> has been 1231 + removed. This option was an association of environment 1232 + variables for Grafana. If you had an expression like 1233 + </para> 1234 + <programlisting language="bash"> 1235 + { 1236 + services.grafana.extraOptions.SECURITY_ADMIN_USER = &quot;foobar&quot;; 1237 + } 1238 + </programlisting> 1239 + <para> 1240 + your Grafana instance was running with 1241 + <literal>GF_SECURITY_ADMIN_USER=foobar</literal> in its 1242 + environment. 1243 + </para> 1244 + <para> 1245 + For the migration, it is recommended to turn it into the 1246 + INI format, i.e. to declare 1247 + </para> 1248 + <programlisting language="bash"> 1249 + { 1250 + services.grafana.settings.security.admin_user = &quot;foobar&quot;; 1251 + } 1252 + </programlisting> 1253 + <para> 1254 + instead. 1255 + </para> 1256 + <para> 1257 + The keys in 1258 + <literal>services.grafana.extraOptions</literal> have the 1259 + format 1260 + <literal>&lt;INI section name&gt;_&lt;Key Name&gt;</literal>. 1261 + Further details are outlined in the 1262 + <link xlink:href="https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#override-configuration-with-environment-variables">configuration 1263 + reference</link>. 1264 + </para> 1265 + <para> 1266 + Alternatively you can also set all your values from 1267 + <literal>extraOptions</literal> to 1268 + <literal>systemd.services.grafana.environment</literal>, 1269 + make sure you don’t forget to add the 1270 + <literal>GF_</literal> prefix though! 1271 + </para> 1272 + </listitem> 1273 + <listitem> 1274 + <para> 1275 + Previously, the options 1276 + <xref linkend="opt-services.grafana.provision.datasources" /> 1277 + and 1278 + <xref linkend="opt-services.grafana.provision.dashboards" /> 1279 + expected lists of datasources or dashboards for the 1280 + <link xlink:href="https://grafana.com/docs/grafana/latest/administration/provisioning/">declarative 1281 + provisioning</link>. 1282 + </para> 1283 + <para> 1284 + To declare lists of 1285 + </para> 1286 + <itemizedlist spacing="compact"> 1287 + <listitem> 1288 + <para> 1289 + <emphasis role="strong">datasources</emphasis>, please 1290 + rename your declarations to 1291 + <xref linkend="opt-services.grafana.provision.datasources.settings.datasources" />. 1292 + </para> 1293 + </listitem> 1294 + <listitem> 1295 + <para> 1296 + <emphasis role="strong">dashboards</emphasis>, please 1297 + rename your declarations to 1298 + <xref linkend="opt-services.grafana.provision.dashboards.settings.providers" />. 1299 + </para> 1300 + </listitem> 1301 + </itemizedlist> 1302 + <para> 1303 + This change was made to support more features for that: 1304 + </para> 1305 + <itemizedlist> 1306 + <listitem> 1307 + <para> 1308 + It’s possible to declare the 1309 + <literal>apiVersion</literal> of your dashboards and 1310 + datasources by 1311 + <xref linkend="opt-services.grafana.provision.datasources.settings.apiVersion" /> 1312 + (or 1313 + <xref linkend="opt-services.grafana.provision.dashboards.settings.apiVersion" />). 1314 + </para> 1315 + </listitem> 1316 + <listitem> 1317 + <para> 1318 + Instead of declaring datasources and dashboards in 1319 + pure Nix, it’s also possible to specify configuration 1320 + files (or directories) with YAML instead using 1321 + <xref linkend="opt-services.grafana.provision.datasources.path" /> 1322 + (or 1323 + <xref linkend="opt-services.grafana.provision.dashboards.path" />. 1324 + This is useful when having provisioning files from 1325 + non-NixOS Grafana instances that you also want to 1326 + deploy to NixOS. 1327 + </para> 1328 + <para> 1329 + <emphasis role="strong">Note:</emphasis> secrets from 1330 + these files will be leaked into the store unless you 1331 + use a 1332 + <link xlink:href="https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider"><emphasis role="strong">file</emphasis>-provider 1333 + or env-var</link> for secrets! 1334 + </para> 1335 + </listitem> 1336 + <listitem> 1337 + <para> 1338 + <xref linkend="opt-services.grafana.provision.notifiers" /> 1339 + is not affected by this change because this feature is 1340 + deprecated by Grafana and will probably removed in 1341 + Grafana 10. It’s recommended to use 1342 + <literal>services.grafana.provision.alerting.contactPoints</literal> 1343 + instead. 1344 + </para> 1345 + </listitem> 1346 + </itemizedlist> 1347 + </listitem> 1348 + </itemizedlist> 1225 1349 </listitem> 1226 1350 <listitem> 1227 1351 <para>
+59 -2
nixos/doc/manual/release-notes/rl-2211.section.md
··· 379 379 380 380 - The `services.matrix-synapse` systemd unit has been hardened. 381 381 382 - - The `services.grafana` options were converted to a [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md) configuration. 382 + - The module `services.grafana` was refactored to be compliant with [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md). To be precise, this means that the following things have changed: 383 + - The newly introduced option [](#opt-services.grafana.settings) is an attribute-set that 384 + will be converted into Grafana's INI format. This means that the configuration from 385 + [Grafana's configuration reference](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/) 386 + can be directly written as attribute-set in Nix within this option. 387 + - The option `services.grafana.extraOptions` has been removed. This option was an association 388 + of environment variables for Grafana. If you had an expression like 389 + 390 + ```nix 391 + { 392 + services.grafana.extraOptions.SECURITY_ADMIN_USER = "foobar"; 393 + } 394 + ``` 395 + 396 + your Grafana instance was running with `GF_SECURITY_ADMIN_USER=foobar` in its environment. 397 + 398 + For the migration, it is recommended to turn it into the INI format, i.e. 399 + to declare 400 + 401 + ```nix 402 + { 403 + services.grafana.settings.security.admin_user = "foobar"; 404 + } 405 + ``` 406 + 407 + instead. 408 + 409 + The keys in `services.grafana.extraOptions` have the format `<INI section name>_<Key Name>`. 410 + Further details are outlined in the [configuration reference](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#override-configuration-with-environment-variables). 411 + 412 + Alternatively you can also set all your values from `extraOptions` to 413 + `systemd.services.grafana.environment`, make sure you don't forget to add 414 + the `GF_` prefix though! 415 + - Previously, the options [](#opt-services.grafana.provision.datasources) and 416 + [](#opt-services.grafana.provision.dashboards) expected lists of datasources 417 + or dashboards for the [declarative provisioning](https://grafana.com/docs/grafana/latest/administration/provisioning/). 418 + 419 + To declare lists of 420 + - **datasources**, please rename your declarations to [](#opt-services.grafana.provision.datasources.settings.datasources). 421 + - **dashboards**, please rename your declarations to [](#opt-services.grafana.provision.dashboards.settings.providers). 422 + 423 + This change was made to support more features for that: 383 424 384 - - 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. 425 + - It's possible to declare the `apiVersion` of your dashboards and datasources 426 + by [](#opt-services.grafana.provision.datasources.settings.apiVersion) (or 427 + [](#opt-services.grafana.provision.dashboards.settings.apiVersion)). 428 + 429 + - Instead of declaring datasources and dashboards in pure Nix, it's also possible 430 + to specify configuration files (or directories) with YAML instead using 431 + [](#opt-services.grafana.provision.datasources.path) (or 432 + [](#opt-services.grafana.provision.dashboards.path). This is useful when having 433 + provisioning files from non-NixOS Grafana instances that you also want to 434 + deploy to NixOS. 435 + 436 + __Note:__ secrets from these files will be leaked into the store unless you use a 437 + [**file**-provider or env-var](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider) for secrets! 438 + 439 + - [](#opt-services.grafana.provision.notifiers) is not affected by this change because 440 + this feature is deprecated by Grafana and will probably removed in Grafana 10. 441 + It's recommended to use `services.grafana.provision.alerting.contactPoints` instead. 385 442 386 443 - 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. 387 444
+141 -133
nixos/modules/services/monitoring/grafana.nix
··· 13 13 settingsFormatIni = pkgs.formats.ini {}; 14 14 configFile = settingsFormatIni.generate "config.ini" cfg.settings; 15 15 16 - datasourceConfiguration = { 17 - apiVersion = 1; 18 - datasources = cfg.provision.datasources; 19 - }; 16 + mkProvisionCfg = name: attr: provisionCfg: 17 + if provisionCfg.path != null 18 + then provisionCfg.path 19 + else 20 + provisioningSettingsFormat.generate "${name}.yaml" 21 + (if provisionCfg.settings != null 22 + then provisionCfg.settings 23 + else { 24 + apiVersion = 1; 25 + ${attr} = []; 26 + }); 20 27 21 - datasourceFileNew = if (cfg.provision.datasources.path == null) then provisioningSettingsFormat.generate "datasource.yaml" cfg.provision.datasources.settings else cfg.provision.datasources.path; 22 - datasourceFile = if (builtins.isList cfg.provision.datasources) then provisioningSettingsFormat.generate "datasource.yaml" datasourceConfiguration else datasourceFileNew; 23 - 24 - dashboardConfiguration = { 25 - apiVersion = 1; 26 - providers = cfg.provision.dashboards; 27 - }; 28 - 29 - dashboardFileNew = if (cfg.provision.dashboards.path == null) then provisioningSettingsFormat.generate "dashboard.yaml" cfg.provision.dashboards.settings else cfg.provision.dashboards.path; 30 - dashboardFile = if (builtins.isList cfg.provision.dashboards) then provisioningSettingsFormat.generate "dashboard.yaml" dashboardConfiguration else dashboardFileNew; 28 + datasourceFileOrDir = mkProvisionCfg "datasource" "datasources" cfg.provision.datasources; 29 + dashboardFileOrDir = mkProvisionCfg "dashboard" "providers" cfg.provision.dashboards; 31 30 32 31 notifierConfiguration = { 33 32 apiVersion = 1; 34 33 notifiers = cfg.provision.notifiers; 35 34 }; 36 35 37 - notifierFile = pkgs.writeText "notifier.yaml" (builtins.toJSON notifierConfiguration); 36 + notifierFileOrDir = pkgs.writeText "notifier.yaml" (builtins.toJSON notifierConfiguration); 38 37 39 38 generateAlertingProvisioningYaml = x: if (cfg.provision.alerting."${x}".path == null) 40 39 then provisioningSettingsFormat.generate "${x}.yaml" cfg.provision.alerting."${x}".settings 41 40 else cfg.provision.alerting."${x}".path; 42 - rulesFile = generateAlertingProvisioningYaml "rules"; 43 - contactPointsFile = generateAlertingProvisioningYaml "contactPoints"; 44 - policiesFile = generateAlertingProvisioningYaml "policies"; 45 - templatesFile = generateAlertingProvisioningYaml "templates"; 46 - muteTimingsFile = generateAlertingProvisioningYaml "muteTimings"; 41 + rulesFileOrDir = generateAlertingProvisioningYaml "rules"; 42 + contactPointsFileOrDir = generateAlertingProvisioningYaml "contactPoints"; 43 + policiesFileOrDir = generateAlertingProvisioningYaml "policies"; 44 + templatesFileOrDir = generateAlertingProvisioningYaml "templates"; 45 + muteTimingsFileOrDir = generateAlertingProvisioningYaml "muteTimings"; 47 46 48 - provisionConfDir = pkgs.runCommand "grafana-provisioning" { } '' 47 + ln = { src, dir, filename }: '' 48 + if [[ -d "${src}" ]]; then 49 + pushd $out/${dir} &>/dev/null 50 + lndir "${src}" 51 + popd &>/dev/null 52 + else 53 + ln -sf ${src} $out/${dir}/${filename}.yaml 54 + fi 55 + ''; 56 + provisionConfDir = pkgs.runCommand "grafana-provisioning" { nativeBuildInputs = [ pkgs.xorg.lndir ]; } '' 49 57 mkdir -p $out/{datasources,dashboards,notifiers,alerting} 50 - ln -sf ${datasourceFile} $out/datasources/datasource.yaml 51 - ln -sf ${dashboardFile} $out/dashboards/dashboard.yaml 52 - ln -sf ${notifierFile} $out/notifiers/notifier.yaml 53 - ln -sf ${rulesFile} $out/alerting/rules.yaml 54 - ln -sf ${contactPointsFile} $out/alerting/contactPoints.yaml 55 - ln -sf ${policiesFile} $out/alerting/policies.yaml 56 - ln -sf ${templatesFile} $out/alerting/templates.yaml 57 - ln -sf ${muteTimingsFile} $out/alerting/muteTimings.yaml 58 + ${ln { src = datasourceFileOrDir; dir = "datasources"; filename = "datasource"; }} 59 + ${ln { src = dashboardFileOrDir; dir = "dashboards"; filename = "dashbaord"; }} 60 + ${ln { src = notifierFileOrDir; dir = "notifiers"; filename = "notifier"; }} 61 + ${ln { src = rulesFileOrDir; dir = "alerting"; filename = "rules"; }} 62 + ${ln { src = contactPointsFileOrDir; dir = "alerting"; filename = "contactPoints"; }} 63 + ${ln { src = policiesFileOrDir; dir = "alerting"; filename = "policies"; }} 64 + ${ln { src = templatesFileOrDir; dir = "alerting"; filename = "templates"; }} 65 + ${ln { src = muteTimingsFileOrDir; dir = "alerting"; filename = "muteTimings"; }} 58 66 ''; 59 67 60 68 # Get a submodule without any embedded metadata: 61 69 _filter = x: filterAttrs (k: v: k != "_module") x; 62 70 71 + # FIXME(@Ma27) remove before 23.05. This is just a helper-type 72 + # because `mkRenamedOptionModule` doesn't work if `foo.bar` is renamed 73 + # to `foo.bar.baz`. 74 + submodule' = module: types.coercedTo 75 + (mkOptionType { 76 + name = "grafana-provision-submodule"; 77 + description = "Wrapper-type for backwards compat of Grafana's declarative provisioning"; 78 + check = x: 79 + if builtins.isList x then 80 + throw '' 81 + Provisioning dashboards and datasources declaratively by 82 + setting `dashboards` or `datasources` to a list is not supported 83 + anymore. Use `services.grafana.provision.datasources.settings.datasources` 84 + (or `services.grafana.provision.dashboards.settings.providers`) instead. 85 + '' 86 + else isAttrs x || isFunction x; 87 + }) 88 + id 89 + (types.submodule module); 90 + 63 91 # http://docs.grafana.org/administration/provisioning/#datasources 64 92 grafanaTypes.datasourceConfig = types.submodule { 65 93 freeformType = provisioningSettingsFormat.type; 66 94 95 + imports = [ 96 + (mkRemovedOptionModule [ "password" ] '' 97 + `services.grafana.provision.datasources.settings.datasources.<name>.password` has been removed 98 + in Grafana 9. Use `secureJsonData` instead. 99 + '') 100 + (mkRemovedOptionModule [ "basicAuthPassword" ] '' 101 + `services.grafana.provision.datasources.settings.datasources.<name>.basicAuthPassword` has been removed 102 + in Grafana 9. Use `secureJsonData` instead. 103 + '') 104 + ]; 105 + 67 106 options = { 68 107 name = mkOption { 69 108 type = types.str; ··· 92 131 type = types.bool; 93 132 default = false; 94 133 description = lib.mdDoc "Allow users to edit datasources from the UI."; 95 - }; 96 - password = mkOption { 97 - type = types.nullOr types.str; 98 - default = null; 99 - description = lib.mdDoc '' 100 - Database password, if used. Please note that the contents of this option 101 - will end up in a world-readable Nix store. Use the file provider 102 - pointing at a reasonably secured file in the local filesystem 103 - to work around that. Look at the documentation for details: 104 - <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider> 105 - ''; 106 - }; 107 - basicAuthPassword = mkOption { 108 - type = types.nullOr types.str; 109 - default = null; 110 - description = lib.mdDoc '' 111 - Basic auth password. Please note that the contents of this option 112 - will end up in a world-readable Nix store. Use the file provider 113 - pointing at a reasonably secured file in the local filesystem 114 - to work around that. Look at the documentation for details: 115 - <https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider> 116 - ''; 117 134 }; 118 135 secureJsonData = mkOption { 119 136 type = types.nullOr types.attrs; ··· 276 293 (mkRemovedOptionModule [ "services" "grafana" "auth" "google" "clientSecretFile" ] '' 277 294 This option has been removed. Use 'services.grafana.settings.google.client_secret' with file provider instead. 278 295 '') 296 + (mkRemovedOptionModule [ "services" "grafana" "extraOptions" ] '' 297 + This option has been removed. Use 'services.grafana.settings' instead. For a detailed migration guide, please 298 + review the release notes of NixOS 22.11. 299 + '') 279 300 280 301 (mkRemovedOptionModule [ "services" "grafana" "auth" "azuread" "tenantId" ] "This option has been deprecated upstream.") 281 302 ]; ··· 330 351 Don't change the value of this option if you are planning to use `services.grafana.provision` options. 331 352 ''; 332 353 default = provisionConfDir; 333 - defaultText = literalExpression '' 334 - pkgs.runCommand "grafana-provisioning" { } \'\' 335 - mkdir -p $out/{datasources,dashboards,notifiers,alerting} 336 - ln -sf ''${datasourceFile} $out/datasources/datasource.yaml 337 - ln -sf ''${dashboardFile} $out/dashboards/dashboard.yaml 338 - ln -sf ''${notifierFile} $out/notifiers/notifier.yaml 339 - ln -sf ''${rulesFile} $out/alerting/rules.yaml 340 - ln -sf ''${contactPointsFile} $out/alerting/contactPoints.yaml 341 - ln -sf ''${policiesFile} $out/alerting/policies.yaml 342 - ln -sf ''${templatesFile} $out/alerting/templates.yaml 343 - ln -sf ''${muteTimingsFile} $out/alerting/muteTimings.yaml 344 - \'\' 345 - ''; 354 + defaultText = "directory with links to files generated from services.grafana.provision"; 346 355 type = types.path; 347 356 }; 348 357 }; ··· 564 573 565 574 datasources = mkOption { 566 575 description = lib.mdDoc '' 567 - Deprecated option for Grafana datasource configuration. Use either 568 - `services.grafana.provision.datasources.settings` or 569 - `services.grafana.provision.datasources.path` instead. 576 + Declaratively provision Grafana's datasources. 570 577 ''; 571 - default = []; 572 - apply = x: if (builtins.isList x) then map _filter x else x; 573 - type = with types; either (listOf grafanaTypes.datasourceConfig) (submodule { 578 + default = {}; 579 + type = submodule' { 574 580 options.settings = mkOption { 575 581 description = lib.mdDoc '' 576 582 Grafana datasource configuration in Nix. Can't be used with 577 - `services.grafana.provision.datasources.path` simultaneously. See 583 + [](#opt-services.grafana.provision.datasources.path) simultaneously. See 578 584 <https://grafana.com/docs/grafana/latest/administration/provisioning/#data-sources> 579 585 for supported options. 580 586 ''; ··· 591 597 description = lib.mdDoc "List of datasources to insert/update."; 592 598 default = []; 593 599 type = types.listOf grafanaTypes.datasourceConfig; 600 + apply = map (flip builtins.removeAttrs [ "password" "basicAuthPassword" ]); 594 601 }; 595 602 596 603 deleteDatasources = mkOption { ··· 630 637 options.path = mkOption { 631 638 description = lib.mdDoc '' 632 639 Path to YAML datasource configuration. Can't be used with 633 - `services.grafana.provision.datasources.settings` simultaneously. 640 + [](#opt-services.grafana.provision.datasources.settings) simultaneously. 641 + Can be either a directory or a single YAML file. Will end up in the store. 634 642 ''; 635 643 default = null; 636 644 type = types.nullOr types.path; 637 645 }; 638 - }); 646 + }; 639 647 }; 640 648 641 649 642 650 dashboards = mkOption { 643 651 description = lib.mdDoc '' 644 - Deprecated option for Grafana dashboard configuration. Use either 645 - `services.grafana.provision.dashboards.settings` or 646 - `services.grafana.provision.dashboards.path` instead. 652 + Declaratively provision Grafana's dashboards. 647 653 ''; 648 - default = []; 649 - apply = x: if (builtins.isList x) then map _filter x else x; 650 - type = with types; either (listOf grafanaTypes.dashboardConfig) (submodule { 654 + default = {}; 655 + type = submodule' { 651 656 options.settings = mkOption { 652 657 description = lib.mdDoc '' 653 658 Grafana dashboard configuration in Nix. Can't be used with 654 - `services.grafana.provision.dashboards.path` simultaneously. See 659 + [](#opt-services.grafana.provision.dashboards.path) simultaneously. See 655 660 <https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards> 656 661 for supported options. 657 662 ''; ··· 684 689 options.path = mkOption { 685 690 description = lib.mdDoc '' 686 691 Path to YAML dashboard configuration. Can't be used with 687 - `services.grafana.provision.dashboards.settings` simultaneously. 692 + [](#opt-services.grafana.provision.dashboards.settings) simultaneously. 693 + Can be either a directory or a single YAML file. Will end up in the store. 688 694 ''; 689 695 default = null; 690 696 type = types.nullOr types.path; 691 697 }; 692 - }); 698 + }; 693 699 }; 694 700 695 701 ··· 706 712 path = mkOption { 707 713 description = lib.mdDoc '' 708 714 Path to YAML rules configuration. Can't be used with 709 - `services.grafana.provision.alerting.rules.settings` simultaneously. 715 + [](#opt-services.grafana.provision.alerting.rules.settings) simultaneously. 716 + Can be either a directory or a single YAML file. Will end up in the store. 710 717 ''; 711 718 default = null; 712 719 type = types.nullOr types.path; ··· 715 722 settings = mkOption { 716 723 description = lib.mdDoc '' 717 724 Grafana rules configuration in Nix. Can't be used with 718 - `services.grafana.provision.alerting.rules.path` simultaneously. See 725 + [](#opt-services.grafana.provision.alerting.rules.path) simultaneously. See 719 726 <https://grafana.com/docs/grafana/latest/administration/provisioning/#rules> 720 727 for supported options. 721 728 ''; ··· 829 836 path = mkOption { 830 837 description = lib.mdDoc '' 831 838 Path to YAML contact points configuration. Can't be used with 832 - `services.grafana.provision.alerting.contactPoints.settings` simultaneously. 839 + [](#opt-services.grafana.provision.alerting.contactPoints.settings) simultaneously. 840 + Can be either a directory or a single YAML file. Will end up in the store. 833 841 ''; 834 842 default = null; 835 843 type = types.nullOr types.path; ··· 838 846 settings = mkOption { 839 847 description = lib.mdDoc '' 840 848 Grafana contact points configuration in Nix. Can't be used with 841 - `services.grafana.provision.alerting.contactPoints.path` simultaneously. See 849 + [](#opt-services.grafana.provision.alerting.contactPoints.path) simultaneously. See 842 850 <https://grafana.com/docs/grafana/latest/administration/provisioning/#contact-points> 843 851 for supported options. 844 852 ''; ··· 852 860 }; 853 861 854 862 contactPoints = mkOption { 855 - description = lib.mdDoc "List of contact points to import or update. Please note that sensitive data will end up in world-readable Nix store."; 863 + description = lib.mdDoc "List of contact points to import or update."; 856 864 default = []; 857 865 type = types.listOf (types.submodule { 858 866 freeformType = provisioningSettingsFormat.type; ··· 909 917 path = mkOption { 910 918 description = lib.mdDoc '' 911 919 Path to YAML notification policies configuration. Can't be used with 912 - `services.grafana.provision.alerting.policies.settings` simultaneously. 920 + [](#opt-services.grafana.provision.alerting.policies.settings) simultaneously. 921 + Can be either a directory or a single YAML file. Will end up in the store. 913 922 ''; 914 923 default = null; 915 924 type = types.nullOr types.path; ··· 918 927 settings = mkOption { 919 928 description = lib.mdDoc '' 920 929 Grafana notification policies configuration in Nix. Can't be used with 921 - `services.grafana.provision.alerting.policies.path` simultaneously. See 930 + [](#opt-services.grafana.provision.alerting.policies.path) simultaneously. See 922 931 <https://grafana.com/docs/grafana/latest/administration/provisioning/#notification-policies> 923 932 for supported options. 924 933 ''; ··· 978 987 path = mkOption { 979 988 description = lib.mdDoc '' 980 989 Path to YAML templates configuration. Can't be used with 981 - `services.grafana.provision.alerting.templates.settings` simultaneously. 990 + [](#opt-services.grafana.provision.alerting.templates.settings) simultaneously. 991 + Can be either a directory or a single YAML file. Will end up in the store. 982 992 ''; 983 993 default = null; 984 994 type = types.nullOr types.path; ··· 987 997 settings = mkOption { 988 998 description = lib.mdDoc '' 989 999 Grafana templates configuration in Nix. Can't be used with 990 - `services.grafana.provision.alerting.templates.path` simultaneously. See 1000 + [](#opt-services.grafana.provision.alerting.templates.path) simultaneously. See 991 1001 <https://grafana.com/docs/grafana/latest/administration/provisioning/#templates> 992 1002 for supported options. 993 1003 ''; ··· 1059 1069 path = mkOption { 1060 1070 description = lib.mdDoc '' 1061 1071 Path to YAML mute timings configuration. Can't be used with 1062 - `services.grafana.provision.alerting.muteTimings.settings` simultaneously. 1072 + [](#opt-services.grafana.provision.alerting.muteTimings.settings) simultaneously. 1073 + Can be either a directory or a single YAML file. Will end up in the store. 1063 1074 ''; 1064 1075 default = null; 1065 1076 type = types.nullOr types.path; ··· 1068 1079 settings = mkOption { 1069 1080 description = lib.mdDoc '' 1070 1081 Grafana mute timings configuration in Nix. Can't be used with 1071 - `services.grafana.provision.alerting.muteTimings.path` simultaneously. See 1082 + [](#opt-services.grafana.provision.alerting.muteTimings.path) simultaneously. See 1072 1083 <https://grafana.com/docs/grafana/latest/administration/provisioning/#mute-timings> 1073 1084 for supported options. 1074 1085 ''; ··· 1159 1170 1160 1171 config = mkIf cfg.enable { 1161 1172 warnings = let 1162 - usesFileProvider = opt: defaultValue: builtins.match "^${defaultValue}$|^\\$__file\\{.*}$" opt != null; 1163 - in flatten [ 1164 - (optional ( 1165 - ! usesFileProvider cfg.settings.database.password "" || 1166 - ! usesFileProvider cfg.settings.security.admin_password "admin" 1167 - ) "Grafana passwords will be stored as plaintext in the Nix store! Use file provider instead.") 1168 - (optional ( 1173 + doesntUseFileProvider = opt: defaultValue: 1169 1174 let 1170 - checkOpts = opt: any (x: x.password != null || x.basicAuthPassword != null || x.secureJsonData != null) opt; 1171 - datasourcesUsed = if (cfg.provision.datasources.settings == null) then [] else cfg.provision.datasources.settings.datasources; 1172 - in if (builtins.isList cfg.provision.datasources) then checkOpts cfg.provision.datasources else checkOpts datasourcesUsed 1173 - ) '' 1174 - Datasource passwords will be stored as plaintext in the Nix store! 1175 - It is not possible to use file provider in provisioning; please provision 1176 - datasources via `services.grafana.provision.datasources.path` instead. 1177 - '') 1175 + regex = "${optionalString (defaultValue != null) "^${defaultValue}$|"}^\\$__(file|env)\\{.*}$|^\\$[^_\\$][^ ]+$"; 1176 + in builtins.match regex opt == null; 1177 + in 1178 + # Ensure that no custom credentials are leaked into the Nix store. Unless the default value 1179 + # is specified, this can be achieved by using the file/env provider: 1180 + # https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#variable-expansion 1178 1181 (optional ( 1179 - any (x: x.secure_settings != null) cfg.provision.notifiers 1180 - ) "Notifier secure settings will be stored as plaintext in the Nix store! Use file provider instead.") 1181 - (optional ( 1182 - builtins.isList cfg.provision.datasources && cfg.provision.datasources != [] 1182 + doesntUseFileProvider cfg.settings.database.password "" || 1183 + doesntUseFileProvider cfg.settings.security.admin_password "admin" 1183 1184 ) '' 1184 - Provisioning Grafana datasources with options has been deprecated. 1185 - Use `services.grafana.provision.datasources.settings` or 1186 - `services.grafana.provision.datasources.path` instead. 1187 - '') 1188 - (optional ( 1189 - builtins.isList cfg.provision.datasources && cfg.provision.dashboards != [] 1185 + Grafana passwords will be stored as plaintext in the Nix store! 1186 + Use file provider or an env-var instead. 1187 + '') 1188 + # Warn about deprecated notifiers. 1189 + ++ (optional (cfg.provision.notifiers != []) '' 1190 + Notifiers are deprecated upstream and will be removed in Grafana 10. 1191 + Use `services.grafana.provision.alerting.contactPoints` instead. 1192 + '') 1193 + # Ensure that `secureJsonData` of datasources provisioned via `datasources.settings` 1194 + # only uses file/env providers. 1195 + ++ (optional ( 1196 + let 1197 + datasourcesToCheck = optionals 1198 + (cfg.provision.datasources.settings != null) 1199 + cfg.provision.datasources.settings.datasources; 1200 + declarationUnsafe = { secureJsonData, ... }: 1201 + secureJsonData != null 1202 + && any (flip doesntUseFileProvider null) (attrValues secureJsonData); 1203 + in any declarationUnsafe datasourcesToCheck 1190 1204 ) '' 1191 - Provisioning Grafana dashboards with options has been deprecated. 1192 - Use `services.grafana.provision.dashboards.settings` or 1193 - `services.grafana.provision.dashboards.path` instead. 1194 - '') 1195 - (optional ( 1196 - cfg.provision.notifiers != [] 1197 - ) '' 1198 - Notifiers are deprecated upstream and will be removed in Grafana 10. 1199 - Use `services.grafana.provision.alerting.contactPoints` instead. 1200 - '') 1201 - ]; 1205 + Declarations in the `secureJsonData`-block of a datasource will be leaked to the 1206 + Nix store unless a file-provider or an env-var is used! 1207 + '') 1208 + ++ (optional ( 1209 + any (x: x.secure_settings != null) cfg.provision.notifiers 1210 + ) "Notifier secure settings will be stored as plaintext in the Nix store! Use file provider instead."); 1202 1211 1203 1212 environment.systemPackages = [ cfg.package ]; 1204 1213 1205 1214 assertions = [ 1206 1215 { 1207 - assertion = if (builtins.isList cfg.provision.datasources) then true else cfg.provision.datasources.settings == null || cfg.provision.datasources.path == null; 1216 + assertion = cfg.provision.datasources.settings == null || cfg.provision.datasources.path == null; 1208 1217 message = "Cannot set both datasources settings and datasources path"; 1209 1218 } 1210 1219 { ··· 1213 1222 ({ type, access, ... }: type == "prometheus" -> access != "direct") 1214 1223 opt; 1215 1224 in 1216 - if (builtins.isList cfg.provision.datasources) then prometheusIsNotDirect cfg.provision.datasources 1217 - else cfg.provision.datasources.settings == null || prometheusIsNotDirect cfg.provision.datasources.settings.datasources; 1225 + cfg.provision.datasources.settings == null || prometheusIsNotDirect cfg.provision.datasources.settings.datasources; 1218 1226 message = "For datasources of type `prometheus`, the `direct` access mode is not supported anymore (since Grafana 9.2.0)"; 1219 1227 } 1220 1228 { 1221 - assertion = if (builtins.isList cfg.provision.dashboards) then true else cfg.provision.dashboards.settings == null || cfg.provision.dashboards.path == null; 1229 + assertion = cfg.provision.dashboards.settings == null || cfg.provision.dashboards.path == null; 1222 1230 message = "Cannot set both dashboards settings and dashboards path"; 1223 1231 } 1224 1232 {
+63 -41
nixos/tests/grafana/provision/default.nix
··· 17 17 18 18 security = { 19 19 admin_user = "testadmin"; 20 - admin_password = "snakeoilpwd"; 20 + admin_password = "$__file{${pkgs.writeText "pwd" "snakeoilpwd"}}"; 21 21 }; 22 22 }; 23 23 }; ··· 28 28 }; 29 29 30 30 extraNodeConfs = { 31 - provisionOld = { 31 + provisionLegacyNotifiers = { 32 32 services.grafana.provision = { 33 - datasources = [{ 34 - name = "Test Datasource"; 35 - type = "testdata"; 36 - access = "proxy"; 37 - uid = "test_datasource"; 38 - }]; 39 - 40 - dashboards = [{ options.path = "/var/lib/grafana/dashboards"; }]; 41 - 33 + datasources.settings = { 34 + apiVersion = 1; 35 + datasources = [{ 36 + name = "Test Datasource"; 37 + type = "testdata"; 38 + access = "proxy"; 39 + uid = "test_datasource"; 40 + }]; 41 + }; 42 + dashboards.settings = { 43 + apiVersion = 1; 44 + providers = [{ 45 + name = "default"; 46 + options.path = "/var/lib/grafana/dashboards"; 47 + }]; 48 + }; 42 49 notifiers = [{ 43 50 uid = "test_notifiers"; 44 51 name = "Test Notifiers"; ··· 50 57 }]; 51 58 }; 52 59 }; 53 - 54 60 provisionNix = { 55 61 services.grafana.provision = { 56 62 datasources.settings = { ··· 157 163 }; 158 164 }; 159 165 }; 166 + 167 + provisionYamlDirs = let 168 + mkdir = p: pkgs.writeTextDir (baseNameOf p) (builtins.readFile p); 169 + in { 170 + services.grafana.provision = { 171 + datasources.path = mkdir ./datasources.yaml; 172 + dashboards.path = mkdir ./dashboards.yaml; 173 + alerting = { 174 + rules.path = mkdir ./rules.yaml; 175 + contactPoints.path = mkdir ./contact-points.yaml; 176 + policies.path = mkdir ./policies.yaml; 177 + templates.path = mkdir ./templates.yaml; 178 + muteTimings.path = mkdir ./mute-timings.yaml; 179 + }; 180 + }; 181 + }; 160 182 }; 161 183 162 184 nodes = builtins.mapAttrs (_: val: mkMerge [ val baseGrafanaConf ]) extraNodeConfs; ··· 172 194 testScript = '' 173 195 start_all() 174 196 175 - nodeOld = ("Nix (old format)", provisionOld) 176 197 nodeNix = ("Nix (new format)", provisionNix) 177 198 nodeYaml = ("Nix (YAML)", provisionYaml) 199 + nodeYamlDir = ("Nix (YAML in dirs)", provisionYamlDirs) 178 200 179 - for nodeInfo in [nodeOld, nodeNix, nodeYaml]: 180 - with subtest(f"Should start provision node: {nodeInfo[0]}"): 181 - nodeInfo[1].wait_for_unit("grafana.service") 182 - nodeInfo[1].wait_for_open_port(3000) 201 + for description, machine in [nodeNix, nodeYaml, nodeYamlDir]: 202 + with subtest(f"Should start provision node: {description}"): 203 + machine.wait_for_unit("grafana.service") 204 + machine.wait_for_open_port(3000) 183 205 184 - with subtest(f"Successful datasource provision with {nodeInfo[0]}"): 185 - nodeInfo[1].succeed( 206 + with subtest(f"Successful datasource provision with {description}"): 207 + machine.succeed( 186 208 "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/datasources/uid/test_datasource | grep Test\ Datasource" 187 209 ) 188 210 189 - with subtest(f"Successful dashboard provision with {nodeInfo[0]}"): 190 - nodeInfo[1].succeed( 211 + with subtest(f"Successful dashboard provision with {description}"): 212 + machine.succeed( 191 213 "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/dashboards/uid/test_dashboard | grep Test\ Dashboard" 192 214 ) 193 215 194 - 195 - 196 - with subtest(f"Successful notifiers provision with {nodeOld[0]}"): 197 - nodeOld[1].succeed( 198 - "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/alert-notifications/uid/test_notifiers | grep Test\ Notifiers" 199 - ) 200 - 201 - 202 - 203 - for nodeInfo in [nodeNix, nodeYaml]: 204 - with subtest(f"Successful rule provision with {nodeInfo[0]}"): 205 - nodeInfo[1].succeed( 216 + with subtest(f"Successful rule provision with {description}"): 217 + machine.succeed( 206 218 "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/alert-rules/test_rule | grep Test\ Rule" 207 219 ) 208 220 209 - with subtest(f"Successful contact point provision with {nodeInfo[0]}"): 210 - nodeInfo[1].succeed( 221 + with subtest(f"Successful contact point provision with {description}"): 222 + machine.succeed( 211 223 "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/contact-points | grep Test\ Contact\ Point" 212 224 ) 213 225 214 - with subtest(f"Successful policy provision with {nodeInfo[0]}"): 215 - nodeInfo[1].succeed( 226 + with subtest(f"Successful policy provision with {description}"): 227 + machine.succeed( 216 228 "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/policies | grep Test\ Contact\ Point" 217 229 ) 218 230 219 - with subtest(f"Successful template provision with {nodeInfo[0]}"): 220 - nodeInfo[1].succeed( 231 + with subtest(f"Successful template provision with {description}"): 232 + machine.succeed( 221 233 "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/templates | grep Test\ Template" 222 234 ) 223 235 224 - with subtest("Successful mute timings provision with {nodeInfo[0]}"): 225 - nodeInfo[1].succeed( 236 + with subtest("Successful mute timings provision with {description}"): 237 + machine.succeed( 226 238 "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/mute-timings | grep Test\ Mute\ Timing" 227 239 ) 240 + 241 + with subtest("Successful notifiers provision"): 242 + provisionLegacyNotifiers.wait_for_unit("grafana.service") 243 + provisionLegacyNotifiers.wait_for_open_port(3000) 244 + print(provisionLegacyNotifiers.succeed( 245 + "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/alert-notifications/uid/test_notifiers" 246 + )) 247 + provisionLegacyNotifiers.succeed( 248 + "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/alert-notifications/uid/test_notifiers | grep Test\ Notifiers" 249 + ) 228 250 ''; 229 251 })