From 8b20c7bbb089e1b5434fd2608ceb2ed667de27e0 Mon Sep 17 00:00:00 2001 From: Ben Sully Date: Thu, 19 Sep 2024 11:03:02 +0100 Subject: [PATCH] feat: add `GrafanaConfig` struct with a couple of useful keys This adds a new struct, `GrafanaConfig`, to the existing `PluginContext` struct. The config can be used to obtain some useful fields provided by Grafana such as the app URL and service account client secret, if available. This commit doesn't include all of the possible keys that Grafana may provide, but it does include the ones that are most likely to be useful for me. If you need more, please open an issue or PR. --- CHANGELOG.md | 3 + .../src/backend/grafana_config.rs | 71 +++++++++++++++++++ crates/grafana-plugin-sdk/src/backend/mod.rs | 6 ++ 3 files changed, 80 insertions(+) create mode 100644 crates/grafana-plugin-sdk/src/backend/grafana_config.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d9b6229..af56ae1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `AppInstanceSettings::api_version` and `DataSourceInstanceSettings::api_version` fields. +- Add `PluginContext::grafana_config` field. This can be used to access a new struct, + `GrafanaConfig`, which contains the configuration passed to the plugin from Grafana. + Use the methods on `GrafanaConfig` to access the configuration. ### Changed diff --git a/crates/grafana-plugin-sdk/src/backend/grafana_config.rs b/crates/grafana-plugin-sdk/src/backend/grafana_config.rs new file mode 100644 index 00000000..b444b895 --- /dev/null +++ b/crates/grafana-plugin-sdk/src/backend/grafana_config.rs @@ -0,0 +1,71 @@ +use std::collections::HashMap; + +/// The error returned when Grafana does not provide a requested configuration option. +#[derive(Debug, Clone, thiserror::Error)] +#[error("key {key} not found in Grafana config; a more recent version of Grafana may be required")] +pub struct ConfigError { + key: String, +} + +impl ConfigError { + fn from_key(key: String) -> Self { + Self { key } + } +} + +type ConfigResult = std::result::Result; + +/// Configuration passed to the plugin from Grafana. +#[derive(Clone)] +pub struct GrafanaConfig { + config: HashMap, +} + +impl GrafanaConfig { + const APP_URL: &'static str = "GF_APP_URL"; + const APP_CLIENT_SECRET: &'static str = "GF_PLUGIN_APP_CLIENT_SECRET"; + // const CONCURRENT_QUERY_COUNT: &'static str = "GF_CONCURRENT_QUERY_COUNT"; + // const USER_FACING_DEFAULT_ERROR: &'static str = "GF_USER_FACING_DEFAULT_ERROR"; + // const SQL_ROW_LIMIT: &'static str = "GF_SQL_ROW_LIMIT"; + // const SQL_MAX_OPEN_CONNS_DEFAULT: &'static str = "GF_SQL_MAX_OPEN_CONNS_DEFAULT"; + // const SQL_MAX_IDLE_CONNS_DEFAULT: &'static str = "GF_SQL_MAX_IDLE_CONNS_DEFAULT"; + // const SQL_MAX_CONN_LIFETIME_SECONDS_DEFAULT: &'static str = + // "GF_SQL_MAX_CONN_LIFETIME_SECONDS_DEFAULT"; + // const RESPONSE_LIMIT: &'static str = "GF_RESPONSE_LIMIT"; + + pub(crate) fn new(config: HashMap) -> Self { + Self { config } + } + + /// Get the value of a configuration option, if it was provided by Grafana. + fn get(&self, key: &str) -> ConfigResult<&String> { + self.config + .get(key) + .ok_or_else(|| ConfigError::from_key(key.to_string())) + } + + /// Return the URL of the Grafana instance. + pub fn app_url(&self) -> ConfigResult<&String> { + self.get(Self::APP_URL) + } + + /// Return the client secret for the app plugin's service account, if set. + /// + /// Plugins can request a service account be created by Grafana at startup + /// time by using the `iam` field of their `plugin.json` file. This method + /// will then return the client secret for that service account, which can + /// be used to authenticate with the Grafana API. + /// + /// See [this example plugin][example] for a full example of how to use this. + /// + /// [example]: https://github.com/grafana/grafana-plugin-examples/tree/main/examples/app-with-service-account + pub fn plugin_app_client_secret(&self) -> ConfigResult<&String> { + self.get(Self::APP_CLIENT_SECRET) + } +} + +impl std::fmt::Debug for GrafanaConfig { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GrafanaConfig").finish() + } +} diff --git a/crates/grafana-plugin-sdk/src/backend/mod.rs b/crates/grafana-plugin-sdk/src/backend/mod.rs index d025cb34..ca112874 100644 --- a/crates/grafana-plugin-sdk/src/backend/mod.rs +++ b/crates/grafana-plugin-sdk/src/backend/mod.rs @@ -158,6 +158,7 @@ pub use tonic::async_trait; mod data; mod diagnostics; mod error_source; +mod grafana_config; mod noop; mod resource; mod stream; @@ -172,6 +173,7 @@ pub use diagnostics::{ DiagnosticsService, HealthStatus, Payload as MetricsPayload, }; pub use error_source::ErrorSource; +pub use grafana_config::{ConfigError, GrafanaConfig}; pub use resource::{ BoxResourceFuture, BoxResourceStream, CallResourceRequest, ErrIntoHttpResponse, IntoHttpResponse, ResourceService, @@ -1109,6 +1111,9 @@ where pub instance_settings: Option, _json_data: PhantomData, _secure_json_data: PhantomData, + + /// Configuration passed to the plugin from Grafana. + pub grafana_config: GrafanaConfig, } impl TryFrom @@ -1132,6 +1137,7 @@ where instance_settings, _json_data: PhantomData, _secure_json_data: PhantomData, + grafana_config: GrafanaConfig::new(other.grafana_config), }) } }