1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
//! Config data for a munin plugin

// We do not want to write unsafe code
#![forbid(unsafe_code)]

use fastrand;
use log::trace;
use std::{
    env,
    iter::repeat_with,
    path::{Path, PathBuf},
};

/// Plugin configuration.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Config {
    /// The name of the plugin.
    ///
    /// Default is "Simple munin plugin in Rust"
    pub plugin_name: String,

    /// Plugins state directory
    ///
    /// Fallback to /tmp if environment variable MUNIN_PLUGSTATE is
    /// not set.
    pub plugin_statedir: PathBuf,

    /// Cachefile for the plugin
    ///
    /// Plugins that daemonize and continuously fetch data need to
    /// write them somewhere, so that the
    /// [MuninPlugin::fetch](super::MuninPlugin::fetch) function can
    /// output them. The default is a combination of
    /// [Config::plugin_statedir] and a random string, with _munin_ and
    /// _value_ added, in [std::format!] syntax: `"{}.munin.{}.value",
    /// [Config::plugin_statedir], randomstring`.
    pub plugin_cache: PathBuf,

    /// Does munin support dirtyconfig? (Send data after sending config)
    ///
    /// Checks MUNIN_CAP_DIRTYCONFIG environment variable, if set to 1,
    /// this is true, otherwise false.
    pub dirtyconfig: bool,

    /// Does this plugin need to run in background, continuously fetching data?
    ///
    /// Default to false
    pub daemonize: bool,

    /// If plugin uses daemonize, whats the pidfile name?
    ///
    /// Defaults to [Config::plugin_statedir] plus "munin-plugin.pid", using
    /// [Config::new] will set it to
    /// [Config::plugin_statedir]/[Config::plugin_name].pid
    pub pidfile: PathBuf,

    /// Size of buffer for BufWriter for [MuninPlugin::config](super::MuninPlugin::config).
    ///
    /// Defaults to 8192, but if the plugin outputs huge munin
    /// configuration (trivial with multigraph plugins), you may want
    /// to increase this.
    pub config_size: usize,

    /// Size of buffer for BufWriter for [MuninPlugin::fetch](super::MuninPlugin::fetch).
    ///
    /// Defaults to 8192, but if the plugin outputs large datasets, it
    /// is useful to increase this.
    pub fetch_size: usize,
}

impl Config {
    /// Return the plugin state directory as munin wants it - or /tmp
    /// if no environment variable is set.
    fn get_statedir() -> PathBuf {
        PathBuf::from(env::var("MUNIN_PLUGSTATE").unwrap_or_else(|_| String::from("/tmp")))
    }

    /// Create a new Config with defined plugin_name, also setting
    /// [Config::pidfile] and [Config::plugin_cache] to a sensible
    /// value using the [Config::plugin_name].
    ///
    /// # Examples
    ///
    /// ```
    /// # use munin_plugin::config::Config;
    /// let config = Config::new(String::from("great-plugin"));
    /// println!("My pidfile is {:?}", config.pidfile);
    /// ```
    pub fn new(plugin_name: String) -> Self {
        Config::realnew(plugin_name, false)
    }

    /// Create a new Config for a streaming (daemonizing) plugin with
    /// defined plugin_name, also setting [Config::pidfile] and
    /// [Config::plugin_cache] to a sensible value using the
    /// [Config::plugin_name].
    ///
    /// # Examples
    ///
    /// ```
    /// # use munin_plugin::config::Config;
    /// let config = Config::new_daemon(String::from("great-plugin"));
    /// println!("My pidfile is {:?}", config.pidfile);
    /// ```
    pub fn new_daemon(plugin_name: String) -> Self {
        Config::realnew(plugin_name, true)
    }

    /// Actually do the work of creating the config element
    fn realnew(plugin_name: String, daemonize: bool) -> Self {
        trace!("Creating new config for plugin {plugin_name}, daemon: {daemonize}");
        let pd = plugin_name.clone();
        Self {
            plugin_name,
            daemonize: daemonize,
            pidfile: Config::get_statedir().join(format!("{}.pid", pd)),
            plugin_cache: Config::get_statedir().join(format!("munin.{}.value", pd)),
            ..Default::default()
        }
    }
}

/// Useful defaults, if possible based on munin environment.
impl Default for Config {
    /// Set default values, try to read munin environment variables to
    /// fill [Config::plugin_statedir] and [Config::dirtyconfig].
    /// [Config::plugin_statedir] falls back to _/tmp_ if no munin
    /// environment variables are present.
    fn default() -> Self {
        let statedir = Config::get_statedir();
        let insert: String = repeat_with(fastrand::alphanumeric).take(10).collect();
        let cachename = Path::new(&statedir).join(format!("munin.{}.value", insert));
        Self {
            plugin_name: String::from("Simple munin plugin in Rust"),
            plugin_statedir: statedir.clone(),
            plugin_cache: cachename,
            dirtyconfig: match env::var("MUNIN_CAP_DIRTYCONFIG") {
                Ok(val) => val.eq(&"1"),
                Err(_) => false,
            },
            daemonize: false,
            pidfile: statedir.join("munin-plugin.pid"),
            config_size: 8192,
            fetch_size: 8192,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::PathBuf;

    #[test]
    fn test_modconfig() {
        // Whole set of defaults
        let config = Config {
            ..Default::default()
        };
        assert_eq!(
            config.plugin_name,
            String::from("Simple munin plugin in Rust")
        );

        // Use defaults (except for name)
        let mut config2 = Config {
            plugin_name: String::from("Lala"),
            ..Default::default()
        };
        // Is plugin name as given?
        assert_eq!(config2.plugin_name, String::from("Lala"));
        // Defaults as expected?
        assert!(!config2.daemonize);
        assert_eq!(config2.fetch_size, 8192);

        config2.pidfile = PathBuf::new();
        config2.pidfile.push(&config2.plugin_statedir);
        config2.pidfile.push(String::from("Lala.pid"));

        let config3 = Config::new(String::from("Lala"));
        // At this point, the plugin_cache should be different
        assert_ne!(config2, config3);
        config2.plugin_cache = config2.plugin_statedir.join("munin.Lala.value");
        assert_eq!(config2, config3);
    }

    #[test]
    fn test_new_daemon() {
        let config = Config::new_daemon(String::from("great-plugin"));
        assert_eq!(config.plugin_name, String::from("great-plugin"));
        assert_eq!(
            config.plugin_cache,
            PathBuf::from(String::from("/tmp/munin.great-plugin.value"))
        );
        assert!(config.daemonize);
    }
}