1use std::collections::HashMap;
2
3#[derive(Debug, Clone)]
4#[cfg_attr(feature = "manifest", derive(serde::Serialize, serde::Deserialize))]
5pub struct HostManifest {
6 #[serde(default)]
7 #[serde(skip_serializing_if = "HashMap::is_empty")]
8 pub labels: HashMap<String, String>,
9 pub actors: Vec<String>,
10 pub capabilities: Vec<Capability>,
11 pub bindings: Vec<BindingEntry>,
12}
13
14#[derive(Debug, Clone)]
15#[cfg_attr(feature = "manifest", derive(serde::Serialize, serde::Deserialize))]
16pub struct Capability {
17 pub path: String,
18 pub binding_name: Option<String>,
19}
20
21#[derive(Debug, Clone)]
22#[cfg_attr(feature = "manifest", derive(serde::Serialize, serde::Deserialize))]
23pub struct BindingEntry {
24 pub actor: String,
25 pub capability: String,
26 pub binding: Option<String>,
27 pub values: Option<HashMap<String, String>>,
28}
29
30#[cfg(feature = "manifest")]
31use std::{fs::File, io::Read, path::Path};
32#[cfg(feature = "manifest")]
33impl HostManifest {
34 pub fn from_path(
39 path: impl AsRef<Path>,
40 expand_env: bool,
41 ) -> std::result::Result<HostManifest, Box<dyn std::error::Error + Send + Sync>> {
42 let mut contents = String::new();
43 let mut file = File::open(path.as_ref())?;
44 file.read_to_string(&mut contents)?;
45 if expand_env {
46 contents = Self::expand_env(&contents);
47 }
48 match path.as_ref().extension() {
49 Some(e) => {
50 let e = e.to_str().unwrap().to_lowercase(); if e == "yaml" || e == "yml" {
52 serde_yaml::from_str::<HostManifest>(&contents).map_err(|e| e.into())
53 } else {
54 serde_json::from_str::<HostManifest>(&contents).map_err(|e| e.into())
55 }
56 }
57 None => serde_yaml::from_str::<HostManifest>(&contents).map_err(|e| e.into()),
58 }
59 }
60
61 fn expand_env(contents: &str) -> String {
62 let mut options = envmnt::ExpandOptions::new();
63 options.default_to_empty = false; options.expansion_type = Some(envmnt::ExpansionType::UnixBracketsWithDefaults); envmnt::expand(contents, Some(options))
67 }
68}
69
70#[cfg(feature = "manifest")]
71#[cfg(test)]
72mod test {
73 use super::{BindingEntry, Capability};
74 use std::collections::HashMap;
75
76 #[test]
77 fn round_trip() {
78 let manifest = super::HostManifest {
79 labels: HashMap::new(),
80 actors: vec!["a".to_string(), "b".to_string(), "c".to_string()],
81 capabilities: vec![
82 Capability {
83 path: "one".to_string(),
84 binding_name: Some("default".to_string()),
85 },
86 Capability {
87 path: "two".to_string(),
88 binding_name: Some("default".to_string()),
89 },
90 ],
91 bindings: vec![BindingEntry {
92 actor: "a".to_string(),
93 binding: Some("default".to_string()),
94 capability: "wascc:one".to_string(),
95 values: Some(gen_values()),
96 }],
97 };
98 let yaml = serde_yaml::to_string(&manifest).unwrap();
99 assert_eq!(yaml, "---\nactors:\n - a\n - b\n - c\ncapabilities:\n - path: one\n binding_name: default\n - path: two\n binding_name: default\nbindings:\n - actor: a\n capability: \"wascc:one\"\n binding: default\n values:\n ROOT: /tmp");
100 }
101
102 #[test]
103 fn round_trip_with_labels() {
104 let manifest = super::HostManifest {
105 labels: {
106 let mut hm = HashMap::new();
107 hm.insert("test".to_string(), "value".to_string());
108 hm
109 },
110 actors: vec!["a".to_string(), "b".to_string(), "c".to_string()],
111 capabilities: vec![
112 Capability {
113 path: "one".to_string(),
114 binding_name: Some("default".to_string()),
115 },
116 Capability {
117 path: "two".to_string(),
118 binding_name: Some("default".to_string()),
119 },
120 ],
121 bindings: vec![BindingEntry {
122 actor: "a".to_string(),
123 binding: Some("default".to_string()),
124 capability: "wascc:one".to_string(),
125 values: Some(gen_values()),
126 }],
127 };
128 let yaml = serde_yaml::to_string(&manifest).unwrap();
129 assert_eq!(yaml, "---\nlabels:\n test: value\nactors:\n - a\n - b\n - c\ncapabilities:\n - path: one\n binding_name: default\n - path: two\n binding_name: default\nbindings:\n - actor: a\n capability: \"wascc:one\"\n binding: default\n values:\n ROOT: /tmp");
130 }
131
132 #[test]
133 fn env_expansion() {
134 let values = vec![
135 "echo Test",
136 "echo $TEST_EXPAND_ENV_TEMP",
137 "echo ${TEST_EXPAND_ENV_TEMP}",
138 "echo ${TEST_EXPAND_ENV_TMP}",
139 "echo ${TEST_EXPAND_ENV_TEMP:/etc}",
140 "echo ${TEST_EXPAND_ENV_TMP:/etc}",
141 ];
142 let expected = vec![
143 "echo Test",
144 "echo $TEST_EXPAND_ENV_TEMP",
145 "echo /tmp",
146 "echo ${TEST_EXPAND_ENV_TMP}",
147 "echo /tmp",
148 "echo /etc",
149 ];
150
151 envmnt::set("TEST_EXPAND_ENV_TEMP", "/tmp");
152 for (got, expected) in values
153 .iter()
154 .map(|v| super::HostManifest::expand_env(v))
155 .zip(expected.iter())
156 {
157 assert_eq!(*expected, got);
158 }
159 envmnt::remove("TEST_EXPAND_ENV_TEMP");
160 }
161
162 fn gen_values() -> HashMap<String, String> {
163 let mut hm = HashMap::new();
164 hm.insert("ROOT".to_string(), "/tmp".to_string());
165
166 hm
167 }
168}