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
use std::{collections::HashMap, path::Path};
use anyhow::{anyhow, bail, Result};
use async_process::{Command, Stdio};
use async_trait::async_trait;
use futures::future::try_join_all;
use itertools::Itertools;
use log::debug;
use url::Url;
use crate::{providers::envs, Hydration};
use super::Provider;
#[derive(Default)]
pub struct Passbolt {
urls: HashMap<Url, String>,
}
impl Passbolt {
pub fn new() -> Self {
Default::default()
}
}
#[async_trait]
impl Provider for Passbolt {
fn add(&mut self, value: String) -> Result<()> {
match Url::parse(&value) {
std::result::Result::Ok(url) if url.scheme() == "passbolt" => {
self.urls.insert(url, value);
Ok(())
}
_ => bail!("Not a passbolt scheme"),
}
}
async fn resolve(&self, _: &Path) -> Result<Hydration> {
let fetches = self
.urls
.iter()
.into_group_map_by(|(url, _)| url.host().expect("Missing host"))
.into_iter()
.flat_map(|(host, group)| {
group
.into_iter()
.into_group_map_by(|(url, _)| url.path().split('/').nth(1).expect("Missing resource id"))
.into_iter()
.map(|(resource_id, group)| {
let host = host.clone();
async move {
let cmd = [
"passbolt",
"get",
"resource",
// #[cfg(debug_assertions)]
// &format!("--serverAddress=http://{}", host),
// #[cfg(not(debug_assertions))]
&format!("--serverAddress=https://{}", host),
&format!("--id={}", resource_id),
"--json"
];
debug!("Lade run: {}", cmd.join(" "));
let child = match Command::new(cmd[0])
.args(&cmd[1..])
.envs(envs())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.await {
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
bail!("Passbolt CLI not found. Make sure the binary is in your PATH or install it from https://github.com/passbolt/go-passbolt-cli.")
},
Err(e) => {
bail!("Passbolt error: {e}")
},
Ok(child) => child,
};
let loaded =
serde_json::from_slice::<HashMap<String, String>>(&child.stdout)
.map_err(|err| {
let stderr = String::from_utf8_lossy(&child.stderr);
anyhow!("Passbolt error: {err} (stderr: {stderr})")
})?;
let hydration = group
.into_iter()
.map(|(url, value)| {
let var = url.path()
.split('/')
.nth(2)
.expect("Missing field");
(
value.clone(),
loaded
.get(var,
)
.unwrap_or_else(|| panic!(
"Variable not found in Passbolt: {}",
resource_id
))
.clone(),
)
})
.collect::<Hydration>();
debug!("hydration: {:?}", hydration);
Ok(hydration)
}
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
Ok(try_join_all(fetches).await?.into_iter().flatten().collect())
}
}