Skip to main content

autoschematic_core/
bundle.rs

1use std::{
2    collections::HashMap,
3    ffi::OsString,
4    fs::File,
5    io::BufReader,
6    path::{Component, Path, PathBuf},
7    sync::Arc,
8};
9
10use async_trait::async_trait;
11use ron::ser::PrettyConfig;
12use serde::{Deserialize, Serialize};
13
14use crate::{
15    connector::{
16        Connector, ConnectorOutbox, DocIdent, FilterResponse, GetDocResponse, GetResourceResponse, OpExecResponse,
17        PlanResponseElement, SkeletonResponse, TaskExecResponse, VirtToPhyResponse,
18    },
19    diag::DiagnosticResponse,
20    util::RON,
21};
22
23#[derive(Serialize, Deserialize)]
24#[serde(deny_unknown_fields)]
25pub enum BundleMapFile {
26    Bundle,
27    ChildOf { parent: PathBuf },
28}
29
30impl BundleMapFile {
31    pub fn path(prefix: &Path, addr: &Path) -> PathBuf {
32        let mut output = prefix.to_path_buf();
33
34        output.push(".bundle");
35
36        // Join the parent portion of `addr`, if it exists
37        if let Some(parent) = addr.parent() {
38            // Guard against pathological cases like ".." or "." parents
39            // by only pushing normal components
40            for comp in parent.components() {
41                if let Component::Normal(_) = comp {
42                    output.push(comp)
43                }
44            }
45        }
46
47        let mut new_filename = OsString::new();
48        if let Some(fname) = addr.file_name() {
49            new_filename.push(fname);
50        } else {
51            // If there's no file name at all, we'll just use ".bun.ron"
52            // so `new_filename` right now is just "." — that's fine.
53            // We'll end up producing something like "./office/east/ec2/us-east-1/.bun.ron"
54        }
55        new_filename.push(".bun.ron");
56
57        output.push(new_filename);
58
59        output
60    }
61
62    pub fn read(prefix: &Path, addr: &Path) -> anyhow::Result<Option<Self>> {
63        let bundle_path = Self::path(prefix, addr);
64
65        if bundle_path.is_file() {
66            let file = File::open(&bundle_path)?;
67            let reader = BufReader::new(file);
68
69            let bundle: Self = RON.from_reader(reader)?;
70
71            return Ok(Some(bundle));
72        }
73
74        Ok(None)
75    }
76
77    pub fn read_recurse(prefix: &Path, addr: &Path) -> anyhow::Result<Option<Self>> {
78        let bundle_path = Self::path(prefix, addr);
79
80        if bundle_path.is_file() {
81            let file = File::open(&bundle_path)?;
82            let reader = BufReader::new(file);
83
84            let bundle: Self = RON.from_reader(reader)?;
85
86            match &bundle {
87                BundleMapFile::ChildOf { parent } => {
88                    return Self::read_recurse(prefix, parent);
89                }
90                BundleMapFile::Bundle => return Ok(Some(bundle)),
91            }
92        }
93
94        Ok(None)
95    }
96
97    pub fn write(&self, prefix: &Path, addr: &Path) -> anyhow::Result<PathBuf> {
98        let bundle_path = Self::path(prefix, addr);
99        let pretty_config = PrettyConfig::default();
100
101        let contents = RON.to_string_pretty(self, pretty_config)?;
102
103        if let Some(parent) = bundle_path.parent() {
104            std::fs::create_dir_all(parent)?;
105        }
106        if bundle_path.exists() {
107            std::fs::remove_file(&bundle_path)?;
108        }
109
110        std::fs::write(&bundle_path, contents)?;
111
112        Ok(bundle_path)
113    }
114
115    pub fn write_link(prefix: &Path, child: &Path, parent: &Path) -> anyhow::Result<PathBuf> {
116        let child_map = Self::ChildOf {
117            parent: parent.to_owned(),
118        };
119        child_map.write(prefix, child)?;
120        Ok(Self::path(prefix, child))
121    }
122
123    pub fn delete(prefix: &Path, addr: &Path) -> anyhow::Result<Option<PathBuf>> {
124        let path = Self::path(prefix, addr);
125        if path.is_file() {
126            std::fs::remove_file(&path)?;
127            Ok(Some(path))
128        } else {
129            Ok(None)
130        }
131    }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct UnbundleResponseElement {
136    pub addr: PathBuf,
137    pub contents: Vec<u8>,
138}
139
140impl UnbundleResponseElement {}
141
142#[async_trait]
143pub trait Bundle
144where
145    Self: Send + Sync,
146{
147    #[allow(clippy::new_ret_no_self)]
148    async fn new(name: &str, prefix: &Path) -> Result<Arc<dyn Bundle>, anyhow::Error>
149    where
150        Self: Sized;
151
152    async fn init(&self) -> Result<(), anyhow::Error>;
153
154    async fn version(&self) -> Result<String, anyhow::Error>;
155
156    async fn filter(&self, addr: &Path) -> Result<FilterResponse, anyhow::Error>;
157
158    async fn unbundle(&self, addr: &Path, resource: &[u8]) -> anyhow::Result<Vec<UnbundleResponseElement>>;
159
160    async fn get_skeletons(&self) -> Result<Vec<SkeletonResponse>, anyhow::Error> {
161        Ok(Vec::new())
162    }
163    async fn get_docstring(&self, _addr: &Path, _ident: DocIdent) -> anyhow::Result<Option<GetDocResponse>> {
164        Ok(None)
165    }
166    async fn eq(&self, _addr: &Path, a: &[u8], b: &[u8]) -> Result<bool, anyhow::Error> {
167        Ok(a == b)
168    }
169    async fn diag(&self, _addr: &Path, _a: &[u8]) -> Result<Option<DiagnosticResponse>, anyhow::Error> {
170        Ok(None)
171    }
172}
173
174#[async_trait]
175impl Bundle for Arc<dyn Bundle> {
176    async fn new(name: &str, prefix: &Path) -> Result<Arc<dyn Bundle>, anyhow::Error> {
177        return <Arc<dyn Bundle> as Bundle>::new(name, prefix).await;
178    }
179
180    async fn init(&self) -> Result<(), anyhow::Error> {
181        Bundle::init(self.as_ref()).await
182    }
183
184    async fn version(&self) -> Result<String, anyhow::Error> {
185        Bundle::version(self.as_ref()).await
186    }
187
188    async fn filter(&self, addr: &Path) -> Result<FilterResponse, anyhow::Error> {
189        Bundle::filter(self.as_ref(), addr).await
190    }
191
192    async fn get_docstring(&self, addr: &Path, ident: DocIdent) -> anyhow::Result<Option<GetDocResponse>> {
193        Bundle::get_docstring(self.as_ref(), addr, ident).await
194    }
195
196    async fn eq(&self, addr: &Path, a: &[u8], b: &[u8]) -> Result<bool, anyhow::Error> {
197        Bundle::eq(self.as_ref(), addr, a, b).await
198    }
199
200    async fn diag(&self, addr: &Path, a: &[u8]) -> Result<Option<DiagnosticResponse>, anyhow::Error> {
201        Bundle::diag(self.as_ref(), addr, a).await
202    }
203
204    async fn unbundle(&self, addr: &Path, resource: &[u8]) -> anyhow::Result<Vec<UnbundleResponseElement>> {
205        Bundle::unbundle(self.as_ref(), addr, resource).await
206    }
207}
208
209#[async_trait]
210impl Connector for Arc<dyn Bundle> {
211    async fn new(name: &str, prefix: &Path, _outbox: ConnectorOutbox) -> Result<Arc<dyn Connector>, anyhow::Error> {
212        let bundle: Arc<dyn Bundle> = <Arc<dyn Bundle + 'static> as Bundle>::new(name, prefix).await?;
213        Ok(Arc::new(bundle))
214    }
215
216    async fn init(&self) -> Result<(), anyhow::Error> {
217        Bundle::init(self).await
218    }
219
220    async fn version(&self) -> Result<String, anyhow::Error> {
221        Bundle::version(self).await
222    }
223
224    async fn filter(&self, addr: &Path) -> Result<FilterResponse, anyhow::Error> {
225        Bundle::filter(self, addr).await
226    }
227
228    async fn list(&self, _subpath: &Path) -> anyhow::Result<Vec<PathBuf>> {
229        Ok(Vec::new())
230    }
231
232    async fn subpaths(&self) -> anyhow::Result<Vec<PathBuf>> {
233        Ok(Vec::new())
234    }
235
236    async fn get(&self, _addr: &Path) -> Result<Option<GetResourceResponse>, anyhow::Error> {
237        Ok(None)
238    }
239
240    async fn plan(
241        &self,
242        _addr: &Path,
243        _current: Option<Vec<u8>>,
244        _desired: Option<Vec<u8>>,
245    ) -> Result<Vec<PlanResponseElement>, anyhow::Error> {
246        Ok(Vec::new())
247    }
248
249    async fn op_exec(&self, _addr: &Path, _op: &str) -> Result<OpExecResponse, anyhow::Error> {
250        Ok(OpExecResponse {
251            outputs: Some(HashMap::new()),
252            friendly_message: Some(String::from("Bundle: No-op!")),
253        })
254    }
255
256    async fn addr_virt_to_phy(&self, addr: &Path) -> Result<VirtToPhyResponse, anyhow::Error> {
257        Ok(VirtToPhyResponse::Present(addr.into()))
258    }
259
260    async fn addr_phy_to_virt(&self, addr: &Path) -> Result<Option<PathBuf>, anyhow::Error> {
261        Ok(Some(addr.into()))
262    }
263
264    async fn get_skeletons(&self) -> Result<Vec<SkeletonResponse>, anyhow::Error> {
265        Bundle::get_skeletons(self).await
266    }
267
268    async fn get_docstring(&self, addr: &Path, ident: DocIdent) -> anyhow::Result<Option<GetDocResponse>> {
269        Bundle::get_docstring(self, addr, ident).await
270    }
271
272    async fn eq(&self, addr: &Path, a: &[u8], b: &[u8]) -> Result<bool, anyhow::Error> {
273        Bundle::eq(self, addr, a, b).await
274    }
275
276    async fn diag(&self, addr: &Path, a: &[u8]) -> Result<Option<DiagnosticResponse>, anyhow::Error> {
277        Bundle::diag(self, addr, a).await
278    }
279
280    async fn task_exec(
281        &self,
282        _addr: &Path,
283        _body: Vec<u8>,
284        _arg: Option<Vec<u8>>,
285        _state: Option<Vec<u8>>,
286    ) -> anyhow::Result<TaskExecResponse> {
287        Ok(TaskExecResponse::default())
288    }
289
290    async fn unbundle(&self, addr: &Path, bundle: &[u8]) -> anyhow::Result<Vec<UnbundleResponseElement>> {
291        Bundle::unbundle(self, addr, bundle).await
292    }
293}