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 c56cfb10538b..8e97e58f81a6 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
@@ -1206,22 +1206,146 @@ services.github-runner.serviceOverrides.SupplementaryGroups = [
- The services.grafana options were converted
- to a
+ The module services.grafana was refactored
+ to be compliant with
RFC
- 0042 configuration.
-
-
-
-
- The services.grafana.provision.datasources
- and services.grafana.provision.dashboards
- options were converted to a
- RFC
- 0042 configuration. They also now support specifying
- the provisioning YAML file with path
- option.
+ 0042. To be precise, this means that the following
+ things have changed:
+
+
+
+ The newly introduced option
+ is an
+ attribute-set that will be converted into Grafana’s INI
+ format. This means that the configuration from
+ Grafana’s
+ configuration reference can be directly written as
+ attribute-set in Nix within this option.
+
+
+
+
+ The option
+ services.grafana.extraOptions has been
+ removed. This option was an association of environment
+ variables for Grafana. If you had an expression like
+
+
+{
+ services.grafana.extraOptions.SECURITY_ADMIN_USER = "foobar";
+}
+
+
+ your Grafana instance was running with
+ GF_SECURITY_ADMIN_USER=foobar in its
+ environment.
+
+
+ For the migration, it is recommended to turn it into the
+ INI format, i.e. to declare
+
+
+{
+ services.grafana.settings.security.admin_user = "foobar";
+}
+
+
+ instead.
+
+
+ The keys in
+ services.grafana.extraOptions have the
+ format
+ <INI section name>_<Key Name>.
+ Further details are outlined in the
+ configuration
+ reference.
+
+
+ Alternatively you can also set all your values from
+ extraOptions to
+ systemd.services.grafana.environment,
+ make sure you don’t forget to add the
+ GF_ prefix though!
+
+
+
+
+ Previously, the options
+
+ and
+
+ expected lists of datasources or dashboards for the
+ declarative
+ provisioning.
+
+
+ To declare lists of
+
+
+
+
+ datasources, please
+ rename your declarations to
+ .
+
+
+
+
+ dashboards, please
+ rename your declarations to
+ .
+
+
+
+
+ This change was made to support more features for that:
+
+
+
+
+ It’s possible to declare the
+ apiVersion of your dashboards and
+ datasources by
+
+ (or
+ ).
+
+
+
+
+ Instead of declaring datasources and dashboards in
+ pure Nix, it’s also possible to specify configuration
+ files (or directories) with YAML instead using
+
+ (or
+ .
+ This is useful when having provisioning files from
+ non-NixOS Grafana instances that you also want to
+ deploy to NixOS.
+
+
+ Note: secrets from
+ these files will be leaked into the store unless you
+ use a
+ file-provider
+ or env-var for secrets!
+
+
+
+
+
+ is not affected by this change because this feature is
+ deprecated by Grafana and will probably removed in
+ Grafana 10. It’s recommended to use
+ services.grafana.provision.alerting.contactPoints
+ instead.
+
+
+
+
+
diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md
index 5daafa8e986b..ecfba7215c8b 100644
--- a/nixos/doc/manual/release-notes/rl-2211.section.md
+++ b/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -379,9 +379,66 @@ Available as [services.patroni](options.html#opt-services.patroni.enable).
- The `services.matrix-synapse` systemd unit has been hardened.
-- The `services.grafana` options were converted to a [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md) configuration.
+- 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:
+ - The newly introduced option [](#opt-services.grafana.settings) is an attribute-set that
+ will be converted into Grafana's INI format. This means that the configuration from
+ [Grafana's configuration reference](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/)
+ can be directly written as attribute-set in Nix within this option.
+ - The option `services.grafana.extraOptions` has been removed. This option was an association
+ of environment variables for Grafana. If you had an expression like
-- 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.
+ ```nix
+ {
+ services.grafana.extraOptions.SECURITY_ADMIN_USER = "foobar";
+ }
+ ```
+
+ your Grafana instance was running with `GF_SECURITY_ADMIN_USER=foobar` in its environment.
+
+ For the migration, it is recommended to turn it into the INI format, i.e.
+ to declare
+
+ ```nix
+ {
+ services.grafana.settings.security.admin_user = "foobar";
+ }
+ ```
+
+ instead.
+
+ The keys in `services.grafana.extraOptions` have the format `_`.
+ Further details are outlined in the [configuration reference](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#override-configuration-with-environment-variables).
+
+ Alternatively you can also set all your values from `extraOptions` to
+ `systemd.services.grafana.environment`, make sure you don't forget to add
+ the `GF_` prefix though!
+ - Previously, the options [](#opt-services.grafana.provision.datasources) and
+ [](#opt-services.grafana.provision.dashboards) expected lists of datasources
+ or dashboards for the [declarative provisioning](https://grafana.com/docs/grafana/latest/administration/provisioning/).
+
+ To declare lists of
+ - **datasources**, please rename your declarations to [](#opt-services.grafana.provision.datasources.settings.datasources).
+ - **dashboards**, please rename your declarations to [](#opt-services.grafana.provision.dashboards.settings.providers).
+
+ This change was made to support more features for that:
+
+ - It's possible to declare the `apiVersion` of your dashboards and datasources
+ by [](#opt-services.grafana.provision.datasources.settings.apiVersion) (or
+ [](#opt-services.grafana.provision.dashboards.settings.apiVersion)).
+
+ - Instead of declaring datasources and dashboards in pure Nix, it's also possible
+ to specify configuration files (or directories) with YAML instead using
+ [](#opt-services.grafana.provision.datasources.path) (or
+ [](#opt-services.grafana.provision.dashboards.path). This is useful when having
+ provisioning files from non-NixOS Grafana instances that you also want to
+ deploy to NixOS.
+
+ __Note:__ secrets from these files will be leaked into the store unless you use a
+ [**file**-provider or env-var](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider) for secrets!
+
+ - [](#opt-services.grafana.provision.notifiers) is not affected by this change because
+ this feature is deprecated by Grafana and will probably removed in Grafana 10.
+ It's recommended to use `services.grafana.provision.alerting.contactPoints` instead.
- 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.
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 52d5cab9f515..9b3068796d8e 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -13,57 +13,96 @@ let
settingsFormatIni = pkgs.formats.ini {};
configFile = settingsFormatIni.generate "config.ini" cfg.settings;
- datasourceConfiguration = {
- apiVersion = 1;
- datasources = cfg.provision.datasources;
- };
+ mkProvisionCfg = name: attr: provisionCfg:
+ if provisionCfg.path != null
+ then provisionCfg.path
+ else
+ provisioningSettingsFormat.generate "${name}.yaml"
+ (if provisionCfg.settings != null
+ then provisionCfg.settings
+ else {
+ apiVersion = 1;
+ ${attr} = [];
+ });
- datasourceFileNew = if (cfg.provision.datasources.path == null) then provisioningSettingsFormat.generate "datasource.yaml" cfg.provision.datasources.settings else cfg.provision.datasources.path;
- datasourceFile = if (builtins.isList cfg.provision.datasources) then provisioningSettingsFormat.generate "datasource.yaml" datasourceConfiguration else datasourceFileNew;
-
- dashboardConfiguration = {
- apiVersion = 1;
- providers = cfg.provision.dashboards;
- };
-
- dashboardFileNew = if (cfg.provision.dashboards.path == null) then provisioningSettingsFormat.generate "dashboard.yaml" cfg.provision.dashboards.settings else cfg.provision.dashboards.path;
- dashboardFile = if (builtins.isList cfg.provision.dashboards) then provisioningSettingsFormat.generate "dashboard.yaml" dashboardConfiguration else dashboardFileNew;
+ datasourceFileOrDir = mkProvisionCfg "datasource" "datasources" cfg.provision.datasources;
+ dashboardFileOrDir = mkProvisionCfg "dashboard" "providers" cfg.provision.dashboards;
notifierConfiguration = {
apiVersion = 1;
notifiers = cfg.provision.notifiers;
};
- notifierFile = pkgs.writeText "notifier.yaml" (builtins.toJSON notifierConfiguration);
+ notifierFileOrDir = 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";
+ rulesFileOrDir = generateAlertingProvisioningYaml "rules";
+ contactPointsFileOrDir = generateAlertingProvisioningYaml "contactPoints";
+ policiesFileOrDir = generateAlertingProvisioningYaml "policies";
+ templatesFileOrDir = generateAlertingProvisioningYaml "templates";
+ muteTimingsFileOrDir = generateAlertingProvisioningYaml "muteTimings";
- provisionConfDir = pkgs.runCommand "grafana-provisioning" { } ''
+ ln = { src, dir, filename }: ''
+ if [[ -d "${src}" ]]; then
+ pushd $out/${dir} &>/dev/null
+ lndir "${src}"
+ popd &>/dev/null
+ else
+ ln -sf ${src} $out/${dir}/${filename}.yaml
+ fi
+ '';
+ provisionConfDir = pkgs.runCommand "grafana-provisioning" { nativeBuildInputs = [ pkgs.xorg.lndir ]; } ''
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
+ ${ln { src = datasourceFileOrDir; dir = "datasources"; filename = "datasource"; }}
+ ${ln { src = dashboardFileOrDir; dir = "dashboards"; filename = "dashbaord"; }}
+ ${ln { src = notifierFileOrDir; dir = "notifiers"; filename = "notifier"; }}
+ ${ln { src = rulesFileOrDir; dir = "alerting"; filename = "rules"; }}
+ ${ln { src = contactPointsFileOrDir; dir = "alerting"; filename = "contactPoints"; }}
+ ${ln { src = policiesFileOrDir; dir = "alerting"; filename = "policies"; }}
+ ${ln { src = templatesFileOrDir; dir = "alerting"; filename = "templates"; }}
+ ${ln { src = muteTimingsFileOrDir; dir = "alerting"; filename = "muteTimings"; }}
'';
# Get a submodule without any embedded metadata:
_filter = x: filterAttrs (k: v: k != "_module") x;
+ # FIXME(@Ma27) remove before 23.05. This is just a helper-type
+ # because `mkRenamedOptionModule` doesn't work if `foo.bar` is renamed
+ # to `foo.bar.baz`.
+ submodule' = module: types.coercedTo
+ (mkOptionType {
+ name = "grafana-provision-submodule";
+ description = "Wrapper-type for backwards compat of Grafana's declarative provisioning";
+ check = x:
+ if builtins.isList x then
+ throw ''
+ Provisioning dashboards and datasources declaratively by
+ setting `dashboards` or `datasources` to a list is not supported
+ anymore. Use `services.grafana.provision.datasources.settings.datasources`
+ (or `services.grafana.provision.dashboards.settings.providers`) instead.
+ ''
+ else isAttrs x || isFunction x;
+ })
+ id
+ (types.submodule module);
+
# http://docs.grafana.org/administration/provisioning/#datasources
grafanaTypes.datasourceConfig = types.submodule {
freeformType = provisioningSettingsFormat.type;
+ imports = [
+ (mkRemovedOptionModule [ "password" ] ''
+ `services.grafana.provision.datasources.settings.datasources..password` has been removed
+ in Grafana 9. Use `secureJsonData` instead.
+ '')
+ (mkRemovedOptionModule [ "basicAuthPassword" ] ''
+ `services.grafana.provision.datasources.settings.datasources..basicAuthPassword` has been removed
+ in Grafana 9. Use `secureJsonData` instead.
+ '')
+ ];
+
options = {
name = mkOption {
type = types.str;
@@ -93,28 +132,6 @@ let
default = false;
description = lib.mdDoc "Allow users to edit datasources from the UI.";
};
- password = mkOption {
- type = types.nullOr types.str;
- default = null;
- description = lib.mdDoc ''
- Database password, if used. Please note that the contents of this option
- will end up in a world-readable Nix store. Use the file provider
- pointing at a reasonably secured file in the local filesystem
- to work around that. Look at the documentation for details:
-
- '';
- };
- basicAuthPassword = mkOption {
- type = types.nullOr types.str;
- default = null;
- description = lib.mdDoc ''
- Basic auth password. Please note that the contents of this option
- will end up in a world-readable Nix store. Use the file provider
- pointing at a reasonably secured file in the local filesystem
- to work around that. Look at the documentation for details:
-
- '';
- };
secureJsonData = mkOption {
type = types.nullOr types.attrs;
default = null;
@@ -276,6 +293,10 @@ in {
(mkRemovedOptionModule [ "services" "grafana" "auth" "google" "clientSecretFile" ] ''
This option has been removed. Use 'services.grafana.settings.google.client_secret' with file provider instead.
'')
+ (mkRemovedOptionModule [ "services" "grafana" "extraOptions" ] ''
+ This option has been removed. Use 'services.grafana.settings' instead. For a detailed migration guide, please
+ review the release notes of NixOS 22.11.
+ '')
(mkRemovedOptionModule [ "services" "grafana" "auth" "azuread" "tenantId" ] "This option has been deprecated upstream.")
];
@@ -330,19 +351,7 @@ in {
Don't change the value of this option if you are planning to use `services.grafana.provision` options.
'';
default = provisionConfDir;
- defaultText = literalExpression ''
- pkgs.runCommand "grafana-provisioning" { } \'\'
- 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
- \'\'
- '';
+ defaultText = "directory with links to files generated from services.grafana.provision";
type = types.path;
};
};
@@ -564,17 +573,14 @@ in {
datasources = mkOption {
description = lib.mdDoc ''
- Deprecated option for Grafana datasource configuration. Use either
- `services.grafana.provision.datasources.settings` or
- `services.grafana.provision.datasources.path` instead.
+ Declaratively provision Grafana's datasources.
'';
- default = [];
- apply = x: if (builtins.isList x) then map _filter x else x;
- type = with types; either (listOf grafanaTypes.datasourceConfig) (submodule {
+ default = {};
+ type = submodule' {
options.settings = mkOption {
description = lib.mdDoc ''
Grafana datasource configuration in Nix. Can't be used with
- `services.grafana.provision.datasources.path` simultaneously. See
+ [](#opt-services.grafana.provision.datasources.path) simultaneously. See
for supported options.
'';
@@ -591,6 +597,7 @@ in {
description = lib.mdDoc "List of datasources to insert/update.";
default = [];
type = types.listOf grafanaTypes.datasourceConfig;
+ apply = map (flip builtins.removeAttrs [ "password" "basicAuthPassword" ]);
};
deleteDatasources = mkOption {
@@ -630,28 +637,26 @@ in {
options.path = mkOption {
description = lib.mdDoc ''
Path to YAML datasource configuration. Can't be used with
- `services.grafana.provision.datasources.settings` simultaneously.
+ [](#opt-services.grafana.provision.datasources.settings) simultaneously.
+ Can be either a directory or a single YAML file. Will end up in the store.
'';
default = null;
type = types.nullOr types.path;
};
- });
+ };
};
dashboards = mkOption {
description = lib.mdDoc ''
- Deprecated option for Grafana dashboard configuration. Use either
- `services.grafana.provision.dashboards.settings` or
- `services.grafana.provision.dashboards.path` instead.
+ Declaratively provision Grafana's dashboards.
'';
- default = [];
- apply = x: if (builtins.isList x) then map _filter x else x;
- type = with types; either (listOf grafanaTypes.dashboardConfig) (submodule {
+ default = {};
+ type = submodule' {
options.settings = mkOption {
description = lib.mdDoc ''
Grafana dashboard configuration in Nix. Can't be used with
- `services.grafana.provision.dashboards.path` simultaneously. See
+ [](#opt-services.grafana.provision.dashboards.path) simultaneously. See
for supported options.
'';
@@ -684,12 +689,13 @@ in {
options.path = mkOption {
description = lib.mdDoc ''
Path to YAML dashboard configuration. Can't be used with
- `services.grafana.provision.dashboards.settings` simultaneously.
+ [](#opt-services.grafana.provision.dashboards.settings) simultaneously.
+ Can be either a directory or a single YAML file. Will end up in the store.
'';
default = null;
type = types.nullOr types.path;
};
- });
+ };
};
@@ -706,7 +712,8 @@ in {
path = mkOption {
description = lib.mdDoc ''
Path to YAML rules configuration. Can't be used with
- `services.grafana.provision.alerting.rules.settings` simultaneously.
+ [](#opt-services.grafana.provision.alerting.rules.settings) simultaneously.
+ Can be either a directory or a single YAML file. Will end up in the store.
'';
default = null;
type = types.nullOr types.path;
@@ -715,7 +722,7 @@ in {
settings = mkOption {
description = lib.mdDoc ''
Grafana rules configuration in Nix. Can't be used with
- `services.grafana.provision.alerting.rules.path` simultaneously. See
+ [](#opt-services.grafana.provision.alerting.rules.path) simultaneously. See
for supported options.
'';
@@ -829,7 +836,8 @@ in {
path = mkOption {
description = lib.mdDoc ''
Path to YAML contact points configuration. Can't be used with
- `services.grafana.provision.alerting.contactPoints.settings` simultaneously.
+ [](#opt-services.grafana.provision.alerting.contactPoints.settings) simultaneously.
+ Can be either a directory or a single YAML file. Will end up in the store.
'';
default = null;
type = types.nullOr types.path;
@@ -838,7 +846,7 @@ in {
settings = mkOption {
description = lib.mdDoc ''
Grafana contact points configuration in Nix. Can't be used with
- `services.grafana.provision.alerting.contactPoints.path` simultaneously. See
+ [](#opt-services.grafana.provision.alerting.contactPoints.path) simultaneously. See
for supported options.
'';
@@ -852,7 +860,7 @@ in {
};
contactPoints = mkOption {
- description = lib.mdDoc "List of contact points to import or update. Please note that sensitive data will end up in world-readable Nix store.";
+ description = lib.mdDoc "List of contact points to import or update.";
default = [];
type = types.listOf (types.submodule {
freeformType = provisioningSettingsFormat.type;
@@ -909,7 +917,8 @@ in {
path = mkOption {
description = lib.mdDoc ''
Path to YAML notification policies configuration. Can't be used with
- `services.grafana.provision.alerting.policies.settings` simultaneously.
+ [](#opt-services.grafana.provision.alerting.policies.settings) simultaneously.
+ Can be either a directory or a single YAML file. Will end up in the store.
'';
default = null;
type = types.nullOr types.path;
@@ -918,7 +927,7 @@ in {
settings = mkOption {
description = lib.mdDoc ''
Grafana notification policies configuration in Nix. Can't be used with
- `services.grafana.provision.alerting.policies.path` simultaneously. See
+ [](#opt-services.grafana.provision.alerting.policies.path) simultaneously. See
for supported options.
'';
@@ -978,7 +987,8 @@ in {
path = mkOption {
description = lib.mdDoc ''
Path to YAML templates configuration. Can't be used with
- `services.grafana.provision.alerting.templates.settings` simultaneously.
+ [](#opt-services.grafana.provision.alerting.templates.settings) simultaneously.
+ Can be either a directory or a single YAML file. Will end up in the store.
'';
default = null;
type = types.nullOr types.path;
@@ -987,7 +997,7 @@ in {
settings = mkOption {
description = lib.mdDoc ''
Grafana templates configuration in Nix. Can't be used with
- `services.grafana.provision.alerting.templates.path` simultaneously. See
+ [](#opt-services.grafana.provision.alerting.templates.path) simultaneously. See
for supported options.
'';
@@ -1059,7 +1069,8 @@ in {
path = mkOption {
description = lib.mdDoc ''
Path to YAML mute timings configuration. Can't be used with
- `services.grafana.provision.alerting.muteTimings.settings` simultaneously.
+ [](#opt-services.grafana.provision.alerting.muteTimings.settings) simultaneously.
+ Can be either a directory or a single YAML file. Will end up in the store.
'';
default = null;
type = types.nullOr types.path;
@@ -1068,7 +1079,7 @@ in {
settings = mkOption {
description = lib.mdDoc ''
Grafana mute timings configuration in Nix. Can't be used with
- `services.grafana.provision.alerting.muteTimings.path` simultaneously. See
+ [](#opt-services.grafana.provision.alerting.muteTimings.path) simultaneously. See
for supported options.
'';
@@ -1159,52 +1170,50 @@ in {
config = mkIf cfg.enable {
warnings = let
- usesFileProvider = opt: defaultValue: builtins.match "^${defaultValue}$|^\\$__file\\{.*}$" opt != null;
- in flatten [
- (optional (
- ! usesFileProvider cfg.settings.database.password "" ||
- ! usesFileProvider cfg.settings.security.admin_password "admin"
- ) "Grafana passwords will be stored as plaintext in the Nix store! Use file provider instead.")
- (optional (
+ doesntUseFileProvider = opt: defaultValue:
let
- checkOpts = opt: any (x: x.password != null || x.basicAuthPassword != null || x.secureJsonData != null) opt;
- datasourcesUsed = if (cfg.provision.datasources.settings == null) then [] else cfg.provision.datasources.settings.datasources;
- in if (builtins.isList cfg.provision.datasources) then checkOpts cfg.provision.datasources else checkOpts datasourcesUsed
- ) ''
- Datasource passwords will be stored as plaintext in the Nix store!
- It is not possible to use file provider in provisioning; please provision
- datasources via `services.grafana.provision.datasources.path` instead.
- '')
+ regex = "${optionalString (defaultValue != null) "^${defaultValue}$|"}^\\$__(file|env)\\{.*}$|^\\$[^_\\$][^ ]+$";
+ in builtins.match regex opt == null;
+ in
+ # Ensure that no custom credentials are leaked into the Nix store. Unless the default value
+ # is specified, this can be achieved by using the file/env provider:
+ # https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#variable-expansion
(optional (
+ doesntUseFileProvider cfg.settings.database.password "" ||
+ doesntUseFileProvider cfg.settings.security.admin_password "admin"
+ ) ''
+ Grafana passwords will be stored as plaintext in the Nix store!
+ Use file provider or an env-var instead.
+ '')
+ # Warn about deprecated notifiers.
+ ++ (optional (cfg.provision.notifiers != []) ''
+ Notifiers are deprecated upstream and will be removed in Grafana 10.
+ Use `services.grafana.provision.alerting.contactPoints` instead.
+ '')
+ # Ensure that `secureJsonData` of datasources provisioned via `datasources.settings`
+ # only uses file/env providers.
+ ++ (optional (
+ let
+ datasourcesToCheck = optionals
+ (cfg.provision.datasources.settings != null)
+ cfg.provision.datasources.settings.datasources;
+ declarationUnsafe = { secureJsonData, ... }:
+ secureJsonData != null
+ && any (flip doesntUseFileProvider null) (attrValues secureJsonData);
+ in any declarationUnsafe datasourcesToCheck
+ ) ''
+ Declarations in the `secureJsonData`-block of a datasource will be leaked to the
+ Nix store unless a file-provider or an env-var is used!
+ '')
+ ++ (optional (
any (x: x.secure_settings != null) cfg.provision.notifiers
- ) "Notifier secure settings will be stored as plaintext in the Nix store! Use file provider instead.")
- (optional (
- builtins.isList cfg.provision.datasources && cfg.provision.datasources != []
- ) ''
- Provisioning Grafana datasources with options has been deprecated.
- Use `services.grafana.provision.datasources.settings` or
- `services.grafana.provision.datasources.path` instead.
- '')
- (optional (
- builtins.isList cfg.provision.datasources && cfg.provision.dashboards != []
- ) ''
- Provisioning Grafana dashboards with options has been deprecated.
- Use `services.grafana.provision.dashboards.settings` or
- `services.grafana.provision.dashboards.path` instead.
- '')
- (optional (
- cfg.provision.notifiers != []
- ) ''
- Notifiers are deprecated upstream and will be removed in Grafana 10.
- Use `services.grafana.provision.alerting.contactPoints` instead.
- '')
- ];
+ ) "Notifier secure settings will be stored as plaintext in the Nix store! Use file provider instead.");
environment.systemPackages = [ cfg.package ];
assertions = [
{
- assertion = if (builtins.isList cfg.provision.datasources) then true else cfg.provision.datasources.settings == null || cfg.provision.datasources.path == null;
+ assertion = cfg.provision.datasources.settings == null || cfg.provision.datasources.path == null;
message = "Cannot set both datasources settings and datasources path";
}
{
@@ -1213,12 +1222,11 @@ in {
({ type, access, ... }: type == "prometheus" -> access != "direct")
opt;
in
- if (builtins.isList cfg.provision.datasources) then prometheusIsNotDirect cfg.provision.datasources
- else cfg.provision.datasources.settings == null || prometheusIsNotDirect cfg.provision.datasources.settings.datasources;
+ cfg.provision.datasources.settings == null || prometheusIsNotDirect cfg.provision.datasources.settings.datasources;
message = "For datasources of type `prometheus`, the `direct` access mode is not supported anymore (since Grafana 9.2.0)";
}
{
- assertion = if (builtins.isList cfg.provision.dashboards) then true else cfg.provision.dashboards.settings == null || cfg.provision.dashboards.path == null;
+ assertion = cfg.provision.dashboards.settings == null || cfg.provision.dashboards.path == null;
message = "Cannot set both dashboards settings and dashboards path";
}
{
diff --git a/nixos/tests/grafana/provision/default.nix b/nixos/tests/grafana/provision/default.nix
index 7a707ab9fed1..1eb927632eb7 100644
--- a/nixos/tests/grafana/provision/default.nix
+++ b/nixos/tests/grafana/provision/default.nix
@@ -17,7 +17,7 @@ let
security = {
admin_user = "testadmin";
- admin_password = "snakeoilpwd";
+ admin_password = "$__file{${pkgs.writeText "pwd" "snakeoilpwd"}}";
};
};
};
@@ -28,17 +28,24 @@ let
};
extraNodeConfs = {
- provisionOld = {
+ provisionLegacyNotifiers = {
services.grafana.provision = {
- datasources = [{
- name = "Test Datasource";
- type = "testdata";
- access = "proxy";
- uid = "test_datasource";
- }];
-
- dashboards = [{ options.path = "/var/lib/grafana/dashboards"; }];
-
+ datasources.settings = {
+ apiVersion = 1;
+ datasources = [{
+ name = "Test Datasource";
+ type = "testdata";
+ access = "proxy";
+ uid = "test_datasource";
+ }];
+ };
+ dashboards.settings = {
+ apiVersion = 1;
+ providers = [{
+ name = "default";
+ options.path = "/var/lib/grafana/dashboards";
+ }];
+ };
notifiers = [{
uid = "test_notifiers";
name = "Test Notifiers";
@@ -50,7 +57,6 @@ let
}];
};
};
-
provisionNix = {
services.grafana.provision = {
datasources.settings = {
@@ -157,6 +163,22 @@ let
};
};
};
+
+ provisionYamlDirs = let
+ mkdir = p: pkgs.writeTextDir (baseNameOf p) (builtins.readFile p);
+ in {
+ services.grafana.provision = {
+ datasources.path = mkdir ./datasources.yaml;
+ dashboards.path = mkdir ./dashboards.yaml;
+ alerting = {
+ rules.path = mkdir ./rules.yaml;
+ contactPoints.path = mkdir ./contact-points.yaml;
+ policies.path = mkdir ./policies.yaml;
+ templates.path = mkdir ./templates.yaml;
+ muteTimings.path = mkdir ./mute-timings.yaml;
+ };
+ };
+ };
};
nodes = builtins.mapAttrs (_: val: mkMerge [ val baseGrafanaConf ]) extraNodeConfs;
@@ -172,58 +194,58 @@ in {
testScript = ''
start_all()
- nodeOld = ("Nix (old format)", provisionOld)
nodeNix = ("Nix (new format)", provisionNix)
nodeYaml = ("Nix (YAML)", provisionYaml)
+ nodeYamlDir = ("Nix (YAML in dirs)", provisionYamlDirs)
- for nodeInfo in [nodeOld, nodeNix, nodeYaml]:
- with subtest(f"Should start provision node: {nodeInfo[0]}"):
- nodeInfo[1].wait_for_unit("grafana.service")
- nodeInfo[1].wait_for_open_port(3000)
+ for description, machine in [nodeNix, nodeYaml, nodeYamlDir]:
+ with subtest(f"Should start provision node: {description}"):
+ machine.wait_for_unit("grafana.service")
+ machine.wait_for_open_port(3000)
- with subtest(f"Successful datasource provision with {nodeInfo[0]}"):
- nodeInfo[1].succeed(
+ with subtest(f"Successful datasource provision with {description}"):
+ machine.succeed(
"curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/datasources/uid/test_datasource | grep Test\ Datasource"
)
- with subtest(f"Successful dashboard provision with {nodeInfo[0]}"):
- nodeInfo[1].succeed(
+ with subtest(f"Successful dashboard provision with {description}"):
+ machine.succeed(
"curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/dashboards/uid/test_dashboard | grep Test\ Dashboard"
)
-
-
- with subtest(f"Successful notifiers provision with {nodeOld[0]}"):
- nodeOld[1].succeed(
- "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/alert-notifications/uid/test_notifiers | grep Test\ Notifiers"
- )
-
-
-
- for nodeInfo in [nodeNix, nodeYaml]:
- with subtest(f"Successful rule provision with {nodeInfo[0]}"):
- nodeInfo[1].succeed(
+ with subtest(f"Successful rule provision with {description}"):
+ machine.succeed(
"curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/alert-rules/test_rule | grep Test\ Rule"
)
- with subtest(f"Successful contact point provision with {nodeInfo[0]}"):
- nodeInfo[1].succeed(
+ with subtest(f"Successful contact point provision with {description}"):
+ machine.succeed(
"curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/contact-points | grep Test\ Contact\ Point"
)
- with subtest(f"Successful policy provision with {nodeInfo[0]}"):
- nodeInfo[1].succeed(
+ with subtest(f"Successful policy provision with {description}"):
+ machine.succeed(
"curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/policies | grep Test\ Contact\ Point"
)
- with subtest(f"Successful template provision with {nodeInfo[0]}"):
- nodeInfo[1].succeed(
+ with subtest(f"Successful template provision with {description}"):
+ machine.succeed(
"curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/templates | grep Test\ Template"
)
- with subtest("Successful mute timings provision with {nodeInfo[0]}"):
- nodeInfo[1].succeed(
+ with subtest("Successful mute timings provision with {description}"):
+ machine.succeed(
"curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/v1/provisioning/mute-timings | grep Test\ Mute\ Timing"
)
+
+ with subtest("Successful notifiers provision"):
+ provisionLegacyNotifiers.wait_for_unit("grafana.service")
+ provisionLegacyNotifiers.wait_for_open_port(3000)
+ print(provisionLegacyNotifiers.succeed(
+ "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/alert-notifications/uid/test_notifiers"
+ ))
+ provisionLegacyNotifiers.succeed(
+ "curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/alert-notifications/uid/test_notifiers | grep Test\ Notifiers"
+ )
'';
})