nixos/grafana: refactor datasources for RFC42
This commit refactors `services.grafana.provision.datasources` towards the RFC42 style. To preserve backwards compatibility, we have to jump through a ton of hoops, introducing esoteric type signatures and bizarre structs. The Grafana module definition should hopefully become a lot cleaner after a release cycle or two once the old configuration style is completely deprecated.
This commit is contained in:
parent
89e30315e0
commit
0852dc859e
@ -835,11 +835,13 @@
|
|||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The <literal>services.grafana.provision.dashboards</literal>
|
The <literal>services.grafana.provision.datasources</literal>
|
||||||
option was converted to a
|
and <literal>services.grafana.provision.dashboards</literal>
|
||||||
|
options were converted to a
|
||||||
<link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">RFC
|
<link xlink:href="https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md">RFC
|
||||||
0042</link> configuration. It also now supports specifying the
|
0042</link> configuration. They also now support specifying
|
||||||
provisioning YAML file with <literal>path</literal> option.
|
the provisioning YAML file with <literal>path</literal>
|
||||||
|
option.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
|
@ -272,7 +272,7 @@ Available as [services.patroni](options.html#opt-services.patroni.enable).
|
|||||||
|
|
||||||
- The `services.matrix-synapse` systemd unit has been hardened.
|
- The `services.matrix-synapse` systemd unit has been hardened.
|
||||||
|
|
||||||
- The `services.grafana.provision.dashboards` option was converted to a [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md) configuration. It also now supports specifying the provisioning YAML file with `path` option.
|
- 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.
|
||||||
|
|
||||||
- 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.
|
- 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.
|
||||||
|
|
||||||
|
@ -78,7 +78,8 @@ let
|
|||||||
datasources = cfg.provision.datasources;
|
datasources = cfg.provision.datasources;
|
||||||
};
|
};
|
||||||
|
|
||||||
datasourceFile = pkgs.writeText "datasource.yaml" (builtins.toJSON datasourceConfiguration);
|
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 = {
|
dashboardConfiguration = {
|
||||||
apiVersion = 1;
|
apiVersion = 1;
|
||||||
@ -107,6 +108,8 @@ let
|
|||||||
|
|
||||||
# http://docs.grafana.org/administration/provisioning/#datasources
|
# http://docs.grafana.org/administration/provisioning/#datasources
|
||||||
grafanaTypes.datasourceConfig = types.submodule {
|
grafanaTypes.datasourceConfig = types.submodule {
|
||||||
|
freeformType = provisioningSettingsFormat.type;
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
name = mkOption {
|
name = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
@ -121,11 +124,6 @@ let
|
|||||||
default = "proxy";
|
default = "proxy";
|
||||||
description = lib.mdDoc "Access mode. proxy or direct (Server or Browser in the UI). Required.";
|
description = lib.mdDoc "Access mode. proxy or direct (Server or Browser in the UI). Required.";
|
||||||
};
|
};
|
||||||
orgId = mkOption {
|
|
||||||
type = types.int;
|
|
||||||
default = 1;
|
|
||||||
description = lib.mdDoc "Org id. will default to orgId 1 if not specified.";
|
|
||||||
};
|
|
||||||
uid = mkOption {
|
uid = mkOption {
|
||||||
type = types.nullOr types.str;
|
type = types.nullOr types.str;
|
||||||
default = null;
|
default = null;
|
||||||
@ -133,68 +131,47 @@ let
|
|||||||
};
|
};
|
||||||
url = mkOption {
|
url = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
|
default = "localhost";
|
||||||
description = lib.mdDoc "Url of the datasource.";
|
description = lib.mdDoc "Url of the datasource.";
|
||||||
};
|
};
|
||||||
password = mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
default = null;
|
|
||||||
description = lib.mdDoc "Database password, if used.";
|
|
||||||
};
|
|
||||||
user = mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
default = null;
|
|
||||||
description = lib.mdDoc "Database user, if used.";
|
|
||||||
};
|
|
||||||
database = mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
default = null;
|
|
||||||
description = lib.mdDoc "Database name, if used.";
|
|
||||||
};
|
|
||||||
basicAuth = mkOption {
|
|
||||||
type = types.nullOr types.bool;
|
|
||||||
default = null;
|
|
||||||
description = lib.mdDoc "Enable/disable basic auth.";
|
|
||||||
};
|
|
||||||
basicAuthUser = mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
default = null;
|
|
||||||
description = lib.mdDoc "Basic auth username.";
|
|
||||||
};
|
|
||||||
basicAuthPassword = mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
default = null;
|
|
||||||
description = lib.mdDoc "Basic auth password.";
|
|
||||||
};
|
|
||||||
withCredentials = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = false;
|
|
||||||
description = lib.mdDoc "Enable/disable with credentials headers.";
|
|
||||||
};
|
|
||||||
isDefault = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = false;
|
|
||||||
description = lib.mdDoc "Mark as default datasource. Max one per org.";
|
|
||||||
};
|
|
||||||
jsonData = mkOption {
|
|
||||||
type = types.nullOr types.attrs;
|
|
||||||
default = null;
|
|
||||||
description = lib.mdDoc "Datasource specific configuration.";
|
|
||||||
};
|
|
||||||
secureJsonData = mkOption {
|
|
||||||
type = types.nullOr types.attrs;
|
|
||||||
default = null;
|
|
||||||
description = lib.mdDoc "Datasource specific secure configuration.";
|
|
||||||
};
|
|
||||||
version = mkOption {
|
|
||||||
type = types.int;
|
|
||||||
default = 1;
|
|
||||||
description = lib.mdDoc "Version.";
|
|
||||||
};
|
|
||||||
editable = mkOption {
|
editable = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
description = lib.mdDoc "Allow users to edit datasources from the UI.";
|
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:
|
||||||
|
<https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
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:
|
||||||
|
<https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
secureJsonData = mkOption {
|
||||||
|
type = types.nullOr types.attrs;
|
||||||
|
default = null;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Datasource specific secure configuration. 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:
|
||||||
|
<https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#file-provider>
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -425,10 +402,79 @@ in {
|
|||||||
enable = mkEnableOption (lib.mdDoc "provision");
|
enable = mkEnableOption (lib.mdDoc "provision");
|
||||||
|
|
||||||
datasources = mkOption {
|
datasources = mkOption {
|
||||||
description = lib.mdDoc "Grafana datasources configuration.";
|
description = lib.mdDoc ''
|
||||||
|
Deprecated option for Grafana datasource configuration. Use either
|
||||||
|
`services.grafana.provision.datasources.settings` or
|
||||||
|
`services.grafana.provision.datasources.path` instead.
|
||||||
|
'';
|
||||||
default = [];
|
default = [];
|
||||||
type = types.listOf grafanaTypes.datasourceConfig;
|
apply = x: if (builtins.isList x) then map _filter x else x;
|
||||||
apply = x: map _filter x;
|
type = with types; either (listOf grafanaTypes.datasourceConfig) (submodule {
|
||||||
|
options.settings = mkOption {
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Grafana datasource configuration in Nix. Can't be used with
|
||||||
|
`services.grafana.provision.datasources.path` simultaneously. See
|
||||||
|
<link xlink:href="https://grafana.com/docs/grafana/latest/administration/provisioning/#data-sources"/>
|
||||||
|
for supported options.
|
||||||
|
'';
|
||||||
|
default = null;
|
||||||
|
type = types.nullOr (types.submodule {
|
||||||
|
options = {
|
||||||
|
apiVersion = mkOption {
|
||||||
|
description = lib.mdDoc "Config file version.";
|
||||||
|
default = 1;
|
||||||
|
type = types.int;
|
||||||
|
};
|
||||||
|
|
||||||
|
datasources = mkOption {
|
||||||
|
description = lib.mdDoc "List of datasources to insert/update.";
|
||||||
|
default = [];
|
||||||
|
type = types.listOf grafanaTypes.datasourceConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteDatasources = mkOption {
|
||||||
|
description = lib.mdDoc "List of datasources that should be deleted from the database.";
|
||||||
|
default = [];
|
||||||
|
type = types.listOf (types.submodule {
|
||||||
|
options.name = mkOption {
|
||||||
|
description = lib.mdDoc "Name of the datasource to delete.";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
options.orgId = mkOption {
|
||||||
|
description = lib.mdDoc "Organization ID of the datasource to delete.";
|
||||||
|
type = types.int;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
apiVersion = 1;
|
||||||
|
|
||||||
|
datasources = [{
|
||||||
|
name = "Graphite";
|
||||||
|
type = "graphite";
|
||||||
|
}];
|
||||||
|
|
||||||
|
deleteDatasources = [{
|
||||||
|
name = "Graphite";
|
||||||
|
orgId = 1;
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
options.path = mkOption {
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Path to YAML datasource configuration. Can't be used with
|
||||||
|
`services.grafana.provision.datasources.settings` simultaneously.
|
||||||
|
'';
|
||||||
|
default = null;
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
};
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -722,11 +768,21 @@ in {
|
|||||||
cfg.security.adminPassword != opt.security.adminPassword.default
|
cfg.security.adminPassword != opt.security.adminPassword.default
|
||||||
) "Grafana passwords will be stored as plaintext in the Nix store!")
|
) "Grafana passwords will be stored as plaintext in the Nix store!")
|
||||||
(optional (
|
(optional (
|
||||||
any (x: x.password != null || x.basicAuthPassword != null || x.secureJsonData != null) cfg.provision.datasources
|
let
|
||||||
) "Datasource passwords will be stored as plaintext in the Nix store!")
|
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! Use file provider instead.")
|
||||||
(optional (
|
(optional (
|
||||||
any (x: x.secure_settings != null) cfg.provision.notifiers
|
any (x: x.secure_settings != null) cfg.provision.notifiers
|
||||||
) "Notifier secure settings will be stored as plaintext in the Nix store!")
|
) "Notifier secure settings will be stored as plaintext in the Nix store!")
|
||||||
|
(optional (
|
||||||
|
builtins.isList 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 (
|
(optional (
|
||||||
builtins.isList cfg.provision.dashboards
|
builtins.isList cfg.provision.dashboards
|
||||||
) ''
|
) ''
|
||||||
@ -756,9 +812,17 @@ in {
|
|||||||
message = "Cannot set both password and passwordFile";
|
message = "Cannot set both password and passwordFile";
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
assertion = all
|
assertion = if (builtins.isList cfg.provision.datasources) then true else cfg.provision.datasources.settings == null || cfg.provision.datasources.path == null;
|
||||||
|
message = "Cannot set both datasources settings and datasources path";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = let
|
||||||
|
prometheusIsNotDirect = opt: all
|
||||||
({ type, access, ... }: type == "prometheus" -> access != "direct")
|
({ type, access, ... }: type == "prometheus" -> access != "direct")
|
||||||
cfg.provision.datasources;
|
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;
|
||||||
message = "For datasources of type `prometheus`, the `direct` access mode is not supported anymore (since Grafana 9.2.0)";
|
message = "For datasources of type `prometheus`, the `direct` access mode is not supported anymore (since Grafana 9.2.0)";
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
@ -5,5 +5,6 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
basic = import ./basic.nix { inherit system pkgs; };
|
basic = import ./basic.nix { inherit system pkgs; };
|
||||||
|
provision-datasources = import ./provision-datasources { inherit system pkgs; };
|
||||||
provision-dashboards = import ./provision-dashboards { inherit system pkgs; };
|
provision-dashboards = import ./provision-dashboards { inherit system pkgs; };
|
||||||
}
|
}
|
||||||
|
95
nixos/tests/grafana/provision-datasources/default.nix
Normal file
95
nixos/tests/grafana/provision-datasources/default.nix
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
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 = {
|
||||||
|
provisionDatasourceOld = {
|
||||||
|
services.grafana.provision = {
|
||||||
|
datasources = [{
|
||||||
|
name = "Test Datasource";
|
||||||
|
type = "testdata";
|
||||||
|
access = "proxy";
|
||||||
|
uid = "test_datasource";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
provisionDatasourceNix = {
|
||||||
|
services.grafana.provision = {
|
||||||
|
datasources.settings = {
|
||||||
|
apiVersion = 1;
|
||||||
|
datasources = [{
|
||||||
|
name = "Test Datasource";
|
||||||
|
type = "testdata";
|
||||||
|
access = "proxy";
|
||||||
|
uid = "test_datasource";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
provisionDatasourceYaml = {
|
||||||
|
services.grafana.provision.datasources.path = ./provision-datasources.yaml;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes = builtins.listToAttrs (map (provisionType:
|
||||||
|
nameValuePair provisionType (mkMerge [
|
||||||
|
baseGrafanaConf
|
||||||
|
(extraNodeConfs.${provisionType} or {})
|
||||||
|
])) [ "provisionDatasourceOld" "provisionDatasourceNix" "provisionDatasourceYaml" ]);
|
||||||
|
|
||||||
|
in {
|
||||||
|
name = "grafana-provision-datasources";
|
||||||
|
|
||||||
|
meta = with maintainers; {
|
||||||
|
maintainers = [ kfears willibutz ];
|
||||||
|
};
|
||||||
|
|
||||||
|
inherit nodes;
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
with subtest("Successful datasource provision with Nix (old format)"):
|
||||||
|
provisionDatasourceOld.wait_for_unit("grafana.service")
|
||||||
|
provisionDatasourceOld.wait_for_open_port(3000)
|
||||||
|
provisionDatasourceOld.succeed(
|
||||||
|
"curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/datasources/uid/test_datasource | grep Test\ Datasource"
|
||||||
|
)
|
||||||
|
provisionDatasourceOld.shutdown()
|
||||||
|
|
||||||
|
with subtest("Successful datasource provision with Nix (new format)"):
|
||||||
|
provisionDatasourceNix.wait_for_unit("grafana.service")
|
||||||
|
provisionDatasourceNix.wait_for_open_port(3000)
|
||||||
|
provisionDatasourceNix.succeed(
|
||||||
|
"curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/datasources/uid/test_datasource | grep Test\ Datasource"
|
||||||
|
)
|
||||||
|
provisionDatasourceNix.shutdown()
|
||||||
|
|
||||||
|
with subtest("Successful datasource provision with YAML"):
|
||||||
|
provisionDatasourceYaml.wait_for_unit("grafana.service")
|
||||||
|
provisionDatasourceYaml.wait_for_open_port(3000)
|
||||||
|
provisionDatasourceYaml.succeed(
|
||||||
|
"curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/datasources/uid/test_datasource | grep Test\ Datasource"
|
||||||
|
)
|
||||||
|
provisionDatasourceYaml.shutdown()
|
||||||
|
'';
|
||||||
|
})) args
|
@ -0,0 +1,7 @@
|
|||||||
|
apiVersion: 1
|
||||||
|
|
||||||
|
datasources:
|
||||||
|
- name: 'Test Datasource'
|
||||||
|
type: 'testdata'
|
||||||
|
access: 'proxy'
|
||||||
|
uid: 'test_datasource'
|
Loading…
Reference in New Issue
Block a user