From 957e368f3d822a7892d18da9493a8c1d48ee8bef Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Wed, 2 Nov 2022 11:54:07 +0100 Subject: [PATCH] nixos/grafana: `provision.{datasources,dashboards}` can't be a list anymore The hack with `either` had the side-effect that the sub-options of the submodule didn't appear in the manual. I decided to remove this because the "migration" isn't that hard, you just need to fix some module declarations. However, `mkRenamedOptionModule` wouldn't work here because it'd create a "virtual" option for the deprecated path (i.e. `services.grafana.provision.{datasources,dashboards}`), but that's the already a new option, i.e. the submodule for the new stuff. To make sure that you still get errors, I implemented a small hack using `coercedTo` which throws an error if a list is specified (as it would be done on 22.05) which explains what to do instead to make the migration easier. Also, I linkified the options in the manual now to make it easier to navigate between those. --- nixos/modules/services/monitoring/grafana.nix | 111 ++++++++---------- nixos/tests/grafana/provision/default.nix | 70 +++-------- 2 files changed, 67 insertions(+), 114 deletions(-) diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index 52d5cab9f515..3ce52bc13e91 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -13,21 +13,9 @@ let settingsFormatIni = pkgs.formats.ini {}; configFile = settingsFormatIni.generate "config.ini" cfg.settings; - datasourceConfiguration = { - apiVersion = 1; - datasources = cfg.provision.datasources; - }; + datasourceFile = if (cfg.provision.datasources.path == null) then provisioningSettingsFormat.generate "datasource.yaml" cfg.provision.datasources.settings else cfg.provision.datasources.path; - 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; + dashboardFile = if (cfg.provision.dashboards.path == null) then provisioningSettingsFormat.generate "dashboard.yaml" cfg.provision.dashboards.settings else cfg.provision.dashboards.path; notifierConfiguration = { apiVersion = 1; @@ -60,6 +48,26 @@ let # 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; @@ -564,17 +572,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. ''; @@ -630,28 +635,25 @@ 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. ''; 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 +686,12 @@ 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. ''; default = null; type = types.nullOr types.path; }; - }); + }; }; @@ -706,7 +708,7 @@ 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. ''; default = null; type = types.nullOr types.path; @@ -715,7 +717,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 +831,7 @@ 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. ''; default = null; type = types.nullOr types.path; @@ -838,7 +840,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. ''; @@ -909,7 +911,7 @@ 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. ''; default = null; type = types.nullOr types.path; @@ -918,7 +920,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 +980,7 @@ 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. ''; default = null; type = types.nullOr types.path; @@ -987,7 +989,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 +1061,7 @@ 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. ''; default = null; type = types.nullOr types.path; @@ -1068,7 +1070,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. ''; @@ -1168,8 +1170,8 @@ in { (optional ( 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 + datasourcesUsed = optionals (cfg.provision.datasources.settings != null) cfg.provision.datasources.settings.datasources; + in 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 @@ -1178,20 +1180,6 @@ in { (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 != [] ) '' @@ -1204,7 +1192,7 @@ in { 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 +1201,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..c4cb9a54daa5 100644 --- a/nixos/tests/grafana/provision/default.nix +++ b/nixos/tests/grafana/provision/default.nix @@ -28,29 +28,6 @@ let }; extraNodeConfs = { - provisionOld = { - services.grafana.provision = { - datasources = [{ - name = "Test Datasource"; - type = "testdata"; - access = "proxy"; - uid = "test_datasource"; - }]; - - dashboards = [{ options.path = "/var/lib/grafana/dashboards"; }]; - - notifiers = [{ - uid = "test_notifiers"; - name = "Test Notifiers"; - type = "email"; - settings = { - singleEmail = true; - addresses = "test@test.com"; - }; - }]; - }; - }; - provisionNix = { services.grafana.provision = { datasources.settings = { @@ -172,57 +149,46 @@ in { testScript = '' start_all() - nodeOld = ("Nix (old format)", provisionOld) nodeNix = ("Nix (new format)", provisionNix) nodeYaml = ("Nix (YAML)", provisionYaml) - 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]: + 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" ) '';