starship/modules/
openstack.rs

1use yaml_rust2::YamlLoader;
2
3use super::{Context, Module, ModuleConfig};
4
5use crate::configs::openstack::OspConfig;
6use crate::formatter::StringFormatter;
7use crate::utils;
8
9type Cloud = String;
10type Project = String;
11
12fn get_osp_project_from_config(context: &Context, osp_cloud: &str) -> Option<Project> {
13    // Attempt to follow OpenStack standards for clouds.yaml location:
14    // 1st = $PWD/clouds.yaml, 2nd = $HOME/.config/openstack/clouds.yaml, 3rd = /etc/openstack/clouds.yaml
15    let config = [
16        context.get_env("PWD").map(|pwd| pwd + "/clouds.yaml"),
17        context.get_home().map(|home| {
18            home.join(".config/openstack/clouds.yaml")
19                .display()
20                .to_string()
21        }),
22        Some(String::from("/etc/openstack/clouds.yaml")),
23    ];
24
25    config
26        .iter()
27        .filter_map(|file| {
28            let config = utils::read_file(file.as_ref()?).ok()?;
29            let clouds = YamlLoader::load_from_str(config.as_str()).ok()?;
30            clouds.first()?["clouds"][osp_cloud]["auth"]["project_name"]
31                .as_str()
32                .map(ToOwned::to_owned)
33        })
34        .find(|s| !s.is_empty())
35}
36
37fn get_osp_cloud_and_project(context: &Context) -> (Option<Cloud>, Option<Project>) {
38    match (
39        context.get_env("OS_CLOUD"),
40        context.get_env("OS_PROJECT_NAME"),
41    ) {
42        (Some(p), Some(r)) => (Some(p), Some(r)),
43        (None, Some(r)) => (None, Some(r)),
44        (Some(ref p), None) => (Some(p.clone()), get_osp_project_from_config(context, p)),
45        (None, None) => (None, None),
46    }
47}
48
49pub fn module<'a>(context: &'a Context) -> Option<Module<'a>> {
50    let mut module = context.new_module("openstack");
51    let config: OspConfig = OspConfig::try_load(module.config);
52
53    let (osp_cloud, osp_project) = get_osp_cloud_and_project(context);
54
55    osp_cloud.as_ref()?;
56
57    let parsed = StringFormatter::new(config.format).and_then(|formatter| {
58        formatter
59            .map_meta(|variable, _| match variable {
60                "symbol" => Some(config.symbol),
61                _ => None,
62            })
63            .map_style(|variable| match variable {
64                "style" => Some(Ok(config.style)),
65                _ => None,
66            })
67            .map(|variable| match variable {
68                "cloud" => osp_cloud.as_ref().map(Ok),
69                "project" => osp_project.as_ref().map(Ok),
70                _ => None,
71            })
72            .parse(None, Some(context))
73    });
74
75    module.set_segments(match parsed {
76        Ok(segments) => segments,
77        Err(error) => {
78            log::error!("Error in module `openstack`: \n{}", error);
79            return None;
80        }
81    });
82
83    Some(module)
84}
85
86#[cfg(test)]
87mod tests {
88    use crate::test::ModuleRenderer;
89    use nu_ansi_term::Color;
90    use std::fs::File;
91    use std::io::{self, Write};
92
93    #[test]
94    fn parse_valid_config() -> io::Result<()> {
95        let dir = tempfile::tempdir()?;
96        let config_path = dir.path().join("clouds.yaml");
97        let mut file = File::create(config_path)?;
98        file.write_all(
99            b"---
100clouds:
101  corp:
102    auth:
103      auth_url: https://overcloud.example.com:13000/v3
104      project_name: testproject
105    identity_api_version: '3'
106    interface: public
107",
108        )?;
109        let actual = ModuleRenderer::new("openstack")
110            .env("PWD", dir.path().to_str().unwrap())
111            .env("OS_CLOUD", "corp")
112            .config(toml::toml! {
113                [openstack]
114            })
115            .collect();
116        let expected = Some(format!(
117            "on {} ",
118            Color::Yellow.bold().paint("☁️  corp(testproject)")
119        ));
120
121        assert_eq!(actual, expected);
122        dir.close()
123    }
124
125    #[test]
126    fn parse_broken_config() -> io::Result<()> {
127        let dir = tempfile::tempdir()?;
128        let config_path = dir.path().join("clouds.yaml");
129        let mut file = File::create(config_path)?;
130        file.write_all(
131            b"---
132dummy_yaml
133",
134        )?;
135        let actual = ModuleRenderer::new("openstack")
136            .env("PWD", dir.path().to_str().unwrap())
137            .env("OS_CLOUD", "test")
138            .config(toml::toml! {
139                [openstack]
140            })
141            .collect();
142        let expected = Some(format!("on {} ", Color::Yellow.bold().paint("☁️  test")));
143
144        assert_eq!(actual, expected);
145        dir.close()
146    }
147
148    #[test]
149    fn dont_crash_on_empty_config() -> io::Result<()> {
150        let dir = tempfile::tempdir()?;
151        let config_path = dir.path().join("clouds.yaml");
152        let mut file = File::create(config_path)?;
153        file.write_all(b"")?;
154        drop(file);
155        let actual = ModuleRenderer::new("openstack")
156            .env("PWD", dir.path().to_str().unwrap())
157            .env("OS_CLOUD", "test")
158            .config(toml::toml! {
159                [openstack]
160            })
161            .collect();
162        let expected = Some(format!("on {} ", Color::Yellow.bold().paint("☁️  test")));
163
164        assert_eq!(actual, expected);
165        dir.close()
166    }
167}