radicle_ci_broker/
config.rs

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
//! Configuration for the CI broker and related programs.

use std::{
    collections::HashMap,
    fmt,
    path::{Path, PathBuf},
    time::Duration,
};

use duration_str::deserialize_duration;
use serde::{Deserialize, Serialize};

use crate::{filter::EventFilter, sensitive::Sensitive};

const DEFAULT_MAX_RUN_TIME: Duration = Duration::from_secs(3600);
const DEFAULT_STATUS_PAGE_UPDATE_INTERVAL: u64 = 10;

#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
    pub default_adapter: String,
    pub adapters: HashMap<String, Adapter>,
    #[serde(deserialize_with = "deserialize_duration")]
    #[serde(default = "default_max_run_time")]
    pub max_run_time: Duration,
    pub filters: Vec<EventFilter>,
    pub report_dir: Option<PathBuf>,
    pub status_update_interval_seconds: Option<u64>,
    pub db: PathBuf,
}

fn default_max_run_time() -> Duration {
    DEFAULT_MAX_RUN_TIME
}

impl Config {
    pub fn load(filename: &Path) -> Result<Self, ConfigError> {
        let config =
            std::fs::read(filename).map_err(|e| ConfigError::ReadConfig(filename.into(), e))?;
        serde_yml::from_slice(&config).map_err(|e| ConfigError::ParseConfig(filename.into(), e))
    }

    pub fn adapter(&self, name: &str) -> Option<&Adapter> {
        self.adapters.get(name)
    }

    pub fn status_page_update_interval(&self) -> u64 {
        self.status_update_interval_seconds
            .unwrap_or(DEFAULT_STATUS_PAGE_UPDATE_INTERVAL)
    }

    pub fn max_run_time(&self) -> Duration {
        self.max_run_time
    }

    pub fn db(&self) -> &Path {
        &self.db
    }

    pub fn to_json(&self) -> Result<String, ConfigError> {
        serde_json::to_string_pretty(self).map_err(ConfigError::ToJson)
    }
}

impl fmt::Debug for Adapter {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Adapter {{ \n command: {:#?}, \n env: {:#?}, \n sensitive_env: {:#?} }}",
            self.command,
            self.env,
            self.sensitive_env
                .keys()
                .map(|k| (k.to_string(), "***".to_string()))
                .collect::<HashMap<String, String>>()
        )
    }
}

#[derive(Serialize, Deserialize)]
pub struct Adapter {
    pub command: PathBuf,
    pub env: HashMap<String, String>,
    #[serde(default)]
    pub sensitive_env: HashMap<String, Sensitive>,
}

impl Adapter {
    pub fn envs(&self) -> &HashMap<String, String> {
        &self.env
    }

    pub fn sensitive_envs(&self) -> &HashMap<String, Sensitive> {
        &self.sensitive_env
    }
}

/// All possible errors from configuration handling.
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
    /// Can't read config file.
    #[error("could not read config file {0}")]
    ReadConfig(PathBuf, #[source] std::io::Error),

    /// Can't parse config file as YAML.
    #[error("failed to parse configuration file as YAML: {0}")]
    ParseConfig(PathBuf, #[source] serde_yml::Error),

    /// Can't convert configuration into JSON.
    #[error("failed to convert configuration into JSON")]
    ToJson(#[source] serde_json::Error),
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    #[allow(clippy::unwrap_used)]
    fn parse_config_yaml() {
        const YAML: &str = r#"---
default_adapter: foo
adapters: {}
filters: []
db: "foo.db"
max_run_time: 1min
...
"#;

        let cfg: Config = serde_yml::from_str(YAML).unwrap();
        assert_eq!(cfg.max_run_time(), Duration::from_secs(60));
    }

    #[test]
    #[allow(clippy::unwrap_used)]
    fn parse_config_yaml_without_max_run_time() {
        const YAML: &str = r#"---
default_adapter: foo
adapters: {}
filters: []
db: "foo.db"
...
"#;

        let cfg: Config = serde_yml::from_str(YAML).unwrap();
        assert_eq!(cfg.max_run_time(), DEFAULT_MAX_RUN_TIME);
    }
}