munin_plugin/
config.rs

1//! Config data for a munin plugin
2//! SPDX-License-Identifier: MIT AND Apache-2.0
3//! Copyright (C) 2022 Joerg Jaspert <joerg@ganneff.de>
4//!
5
6// We do not want to write unsafe code
7#![forbid(unsafe_code)]
8
9use fastrand;
10use log::trace;
11use std::{
12    env,
13    iter::repeat_with,
14    path::{Path, PathBuf},
15};
16
17/// Plugin configuration.
18#[derive(Clone, Debug, Eq, Hash, PartialEq)]
19pub struct Config {
20    /// The name of the plugin.
21    ///
22    /// Default is "Simple munin plugin in Rust"
23    pub plugin_name: String,
24
25    /// Plugins state directory
26    ///
27    /// Fallback to /tmp if environment variable MUNIN_PLUGSTATE is
28    /// not set.
29    pub plugin_statedir: PathBuf,
30
31    /// Cachefile for the plugin
32    ///
33    /// Plugins that daemonize and continuously fetch data need to
34    /// write them somewhere, so that the
35    /// [MuninPlugin::fetch](super::MuninPlugin::fetch) function can
36    /// output them. The default is a combination of
37    /// [Config::plugin_statedir] and a random string, with _munin_ and
38    /// _value_ added, in [std::format!] syntax: `"{}.munin.{}.value",
39    /// [Config::plugin_statedir], randomstring`.
40    pub plugin_cache: PathBuf,
41
42    /// Does munin support dirtyconfig? (Send data after sending config)
43    ///
44    /// Checks MUNIN_CAP_DIRTYCONFIG environment variable, if set to 1,
45    /// this is true, otherwise false.
46    pub dirtyconfig: bool,
47
48    /// Does this plugin need to run in background, continuously fetching data?
49    ///
50    /// Default to false
51    pub daemonize: bool,
52
53    /// If plugin uses daemonize, whats the pidfile name?
54    ///
55    /// Defaults to [Config::plugin_statedir] plus "munin-plugin.pid", using
56    /// [Config::new] will set it to
57    /// [Config::plugin_statedir]/[Config::plugin_name].pid
58    pub pidfile: PathBuf,
59
60    /// Size of buffer for BufWriter for [MuninPlugin::config](super::MuninPlugin::config).
61    ///
62    /// Defaults to 8192, but if the plugin outputs huge munin
63    /// configuration (trivial with multigraph plugins), you may want
64    /// to increase this.
65    pub config_size: usize,
66
67    /// Size of buffer for BufWriter for [MuninPlugin::fetch](super::MuninPlugin::fetch).
68    ///
69    /// Defaults to 8192, but if the plugin outputs large datasets, it
70    /// is useful to increase this.
71    pub fetch_size: usize,
72}
73
74impl Config {
75    /// Return the plugin state directory as munin wants it - or /tmp
76    /// if no environment variable is set.
77    fn get_statedir() -> PathBuf {
78        PathBuf::from(env::var("MUNIN_PLUGSTATE").unwrap_or_else(|_| String::from("/tmp")))
79    }
80
81    /// Create a new Config with defined plugin_name, also setting
82    /// [Config::pidfile] and [Config::plugin_cache] to a sensible
83    /// value using the [Config::plugin_name].
84    ///
85    /// # Examples
86    ///
87    /// ```
88    /// # use munin_plugin::config::Config;
89    /// let config = Config::new(String::from("great-plugin"));
90    /// println!("My pidfile is {:?}", config.pidfile);
91    /// ```
92    pub fn new(plugin_name: String) -> Self {
93        Config::realnew(plugin_name, false)
94    }
95
96    /// Create a new Config for a streaming (daemonizing) plugin with
97    /// defined plugin_name, also setting [Config::pidfile] and
98    /// [Config::plugin_cache] to a sensible value using the
99    /// [Config::plugin_name].
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// # use munin_plugin::config::Config;
105    /// let config = Config::new_daemon(String::from("great-plugin"));
106    /// println!("My pidfile is {:?}", config.pidfile);
107    /// ```
108    pub fn new_daemon(plugin_name: String) -> Self {
109        Config::realnew(plugin_name, true)
110    }
111
112    /// Actually do the work of creating the config element
113    fn realnew(plugin_name: String, daemonize: bool) -> Self {
114        trace!("Creating new config for plugin {plugin_name}, daemon: {daemonize}");
115        let pd = plugin_name.clone();
116        Self {
117            plugin_name,
118            daemonize: daemonize,
119            pidfile: Config::get_statedir().join(format!("{}.pid", pd)),
120            plugin_cache: Config::get_statedir().join(format!("munin.{}.value", pd)),
121            ..Default::default()
122        }
123    }
124}
125
126/// Useful defaults, if possible based on munin environment.
127impl Default for Config {
128    /// Set default values, try to read munin environment variables to
129    /// fill [Config::plugin_statedir] and [Config::dirtyconfig].
130    /// [Config::plugin_statedir] falls back to _/tmp_ if no munin
131    /// environment variables are present.
132    fn default() -> Self {
133        let statedir = Config::get_statedir();
134        let insert: String = repeat_with(fastrand::alphanumeric).take(10).collect();
135        let cachename = Path::new(&statedir).join(format!("munin.{}.value", insert));
136        Self {
137            plugin_name: String::from("Simple munin plugin in Rust"),
138            plugin_statedir: statedir.clone(),
139            plugin_cache: cachename,
140            dirtyconfig: match env::var("MUNIN_CAP_DIRTYCONFIG") {
141                Ok(val) => val.eq(&"1"),
142                Err(_) => false,
143            },
144            daemonize: false,
145            pidfile: statedir.join("munin-plugin.pid"),
146            config_size: 8192,
147            fetch_size: 8192,
148        }
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    use std::path::PathBuf;
156
157    #[test]
158    fn test_modconfig() {
159        // Whole set of defaults
160        let config = Config {
161            ..Default::default()
162        };
163        assert_eq!(
164            config.plugin_name,
165            String::from("Simple munin plugin in Rust")
166        );
167
168        // Use defaults (except for name)
169        let mut config2 = Config {
170            plugin_name: String::from("Lala"),
171            ..Default::default()
172        };
173        // Is plugin name as given?
174        assert_eq!(config2.plugin_name, String::from("Lala"));
175        // Defaults as expected?
176        assert!(!config2.daemonize);
177        assert_eq!(config2.fetch_size, 8192);
178
179        config2.pidfile = PathBuf::new();
180        config2.pidfile.push(&config2.plugin_statedir);
181        config2.pidfile.push(String::from("Lala.pid"));
182
183        let config3 = Config::new(String::from("Lala"));
184        // At this point, the plugin_cache should be different
185        assert_ne!(config2, config3);
186        config2.plugin_cache = config2.plugin_statedir.join("munin.Lala.value");
187        assert_eq!(config2, config3);
188    }
189
190    #[test]
191    fn test_new_daemon() {
192        let config = Config::new_daemon(String::from("great-plugin"));
193        assert_eq!(config.plugin_name, String::from("great-plugin"));
194        assert_eq!(
195            config.plugin_cache,
196            PathBuf::from(String::from("/tmp/munin.great-plugin.value"))
197        );
198        assert!(config.daemonize);
199    }
200}