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}