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
6 changed files with 242 additions and 73 deletions
|
@ -835,11 +835,13 @@
|
|||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
The <literal>services.grafana.provision.dashboards</literal>
|
||||
option was converted to a
|
||||
The <literal>services.grafana.provision.datasources</literal>
|
||||
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
|
||||
0042</link> configuration. It also now supports specifying the
|
||||
provisioning YAML file with <literal>path</literal> option.
|
||||
0042</link> configuration. They also now support specifying
|
||||
the provisioning YAML file with <literal>path</literal>
|
||||
option.
|
||||
</para>
|
||||
</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.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.
|
||||
|
||||
|
|
|
@ -78,7 +78,8 @@ let
|
|||
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 = {
|
||||
apiVersion = 1;
|
||||
|
@ -107,6 +108,8 @@ let
|
|||
|
||||
# http://docs.grafana.org/administration/provisioning/#datasources
|
||||
grafanaTypes.datasourceConfig = types.submodule {
|
||||
freeformType = provisioningSettingsFormat.type;
|
||||
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
|
@ -121,11 +124,6 @@ let
|
|||
default = "proxy";
|
||||
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 {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
|
@ -133,68 +131,47 @@ let
|
|||
};
|
||||
url = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
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 {
|
||||
type = types.bool;
|
||||
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:
|
||||
<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");
|
||||
|
||||
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 = [];
|
||||
apply = x: if (builtins.isList x) then map _filter x else 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;
|
||||
apply = x: map _filter x;
|
||||
};
|
||||
|
||||
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
|
||||
) "Grafana passwords will be stored as plaintext in the Nix store!")
|
||||
(optional (
|
||||
any (x: x.password != null || x.basicAuthPassword != null || x.secureJsonData != null) cfg.provision.datasources
|
||||
) "Datasource passwords will be stored as plaintext in the Nix store!")
|
||||
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! Use file provider instead.")
|
||||
(optional (
|
||||
any (x: x.secure_settings != null) cfg.provision.notifiers
|
||||
) "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 (
|
||||
builtins.isList cfg.provision.dashboards
|
||||
) ''
|
||||
|
@ -756,9 +812,17 @@ in {
|
|||
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")
|
||||
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)";
|
||||
}
|
||||
{
|
||||
|
|
|
@ -5,5 +5,6 @@
|
|||
|
||||
{
|
||||
basic = import ./basic.nix { inherit system pkgs; };
|
||||
provision-datasources = import ./provision-datasources { 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 a new issue