Skip to main content

hardpass/
api.rs

1use std::path::{Path, PathBuf};
2use std::sync::Arc;
3
4use anyhow::Result;
5
6use crate::cli::CreateArgs;
7use crate::instance::{InstanceManager, VmInfo};
8use crate::ssh::ExecOutput;
9use crate::state::{
10    AccelMode, GuestArch, HardpassState, InstanceStatus, PortForward, validate_name,
11};
12
13pub struct Hardpass {
14    manager: Arc<InstanceManager>,
15}
16
17impl Hardpass {
18    pub async fn load() -> Result<Self> {
19        let state = HardpassState::load().await?;
20        Ok(Self::from_state(state))
21    }
22
23    pub async fn with_root(root: impl AsRef<Path>) -> Result<Self> {
24        let state = HardpassState::load_with_root(root.as_ref().to_path_buf()).await?;
25        Ok(Self::from_state(state))
26    }
27
28    pub async fn doctor(&self) -> Result<()> {
29        self.manager.doctor().await
30    }
31
32    pub async fn create(&self, spec: VmSpec) -> Result<Vm> {
33        let name = spec.name.clone();
34        self.manager.create_silent(spec.into_create_args()).await?;
35        self.vm(name)
36    }
37
38    pub fn vm(&self, name: impl Into<String>) -> Result<Vm> {
39        let name = name.into();
40        validate_name(&name)?;
41        Ok(Vm {
42            manager: Arc::clone(&self.manager),
43            name,
44        })
45    }
46
47    fn from_state(state: HardpassState) -> Self {
48        Self {
49            manager: Arc::new(InstanceManager::new(state)),
50        }
51    }
52}
53
54#[derive(Debug, Clone, Default)]
55pub struct VmSpec {
56    pub name: String,
57    pub release: Option<String>,
58    pub arch: Option<GuestArch>,
59    pub accel: Option<AccelMode>,
60    pub cpus: Option<u8>,
61    pub memory_mib: Option<u32>,
62    pub disk_gib: Option<u32>,
63    pub ssh_key: Option<PathBuf>,
64    pub forwards: Vec<PortForward>,
65    pub timeout_secs: Option<u64>,
66    pub cloud_init_user_data: Option<PathBuf>,
67    pub cloud_init_network_config: Option<PathBuf>,
68}
69
70impl VmSpec {
71    pub fn new(name: impl Into<String>) -> Self {
72        Self {
73            name: name.into(),
74            ..Self::default()
75        }
76    }
77
78    pub fn release(mut self, release: impl Into<String>) -> Self {
79        self.release = Some(release.into());
80        self
81    }
82
83    pub fn arch(mut self, arch: GuestArch) -> Self {
84        self.arch = Some(arch);
85        self
86    }
87
88    pub fn accel(mut self, accel: AccelMode) -> Self {
89        self.accel = Some(accel);
90        self
91    }
92
93    pub fn cpus(mut self, cpus: u8) -> Self {
94        self.cpus = Some(cpus);
95        self
96    }
97
98    pub fn memory_mib(mut self, memory_mib: u32) -> Self {
99        self.memory_mib = Some(memory_mib);
100        self
101    }
102
103    pub fn disk_gib(mut self, disk_gib: u32) -> Self {
104        self.disk_gib = Some(disk_gib);
105        self
106    }
107
108    pub fn ssh_key(mut self, ssh_key: impl AsRef<Path>) -> Self {
109        self.ssh_key = Some(ssh_key.as_ref().to_path_buf());
110        self
111    }
112
113    pub fn forward(mut self, host: u16, guest: u16) -> Self {
114        self.forwards.push(PortForward { host, guest });
115        self
116    }
117
118    pub fn timeout_secs(mut self, timeout_secs: u64) -> Self {
119        self.timeout_secs = Some(timeout_secs);
120        self
121    }
122
123    pub fn cloud_init_user_data(mut self, path: impl AsRef<Path>) -> Self {
124        self.cloud_init_user_data = Some(path.as_ref().to_path_buf());
125        self
126    }
127
128    pub fn cloud_init_network_config(mut self, path: impl AsRef<Path>) -> Self {
129        self.cloud_init_network_config = Some(path.as_ref().to_path_buf());
130        self
131    }
132
133    fn into_create_args(self) -> CreateArgs {
134        CreateArgs {
135            name: self.name,
136            release: self.release,
137            arch: self.arch,
138            accel: self.accel,
139            cpus: self.cpus,
140            memory_mib: self.memory_mib,
141            disk_gib: self.disk_gib,
142            ssh_key: self.ssh_key.map(|path| path.display().to_string()),
143            forwards: self
144                .forwards
145                .into_iter()
146                .map(|forward| (forward.host, forward.guest))
147                .collect(),
148            timeout_secs: self.timeout_secs,
149            cloud_init_user_data: self
150                .cloud_init_user_data
151                .map(|path| path.display().to_string()),
152            cloud_init_network_config: self
153                .cloud_init_network_config
154                .map(|path| path.display().to_string()),
155        }
156    }
157}
158
159pub struct Vm {
160    manager: Arc<InstanceManager>,
161    name: String,
162}
163
164impl Vm {
165    pub fn name(&self) -> &str {
166        &self.name
167    }
168
169    pub async fn start(self) -> Result<RunningVm> {
170        self.manager.start_silent(&self.name).await?;
171        Ok(RunningVm { vm: self })
172    }
173
174    pub async fn info(&self) -> Result<VmInfo> {
175        self.manager.vm_info(&self.name).await
176    }
177
178    pub async fn status(&self) -> Result<InstanceStatus> {
179        self.manager.status(&self.name).await
180    }
181
182    pub async fn delete(self) -> Result<()> {
183        self.manager.delete_silent(&self.name).await
184    }
185}
186
187pub struct RunningVm {
188    vm: Vm,
189}
190
191impl RunningVm {
192    pub fn name(&self) -> &str {
193        self.vm.name()
194    }
195
196    pub async fn wait_for_ssh(&self) -> Result<VmInfo> {
197        self.vm.manager.wait_for_ssh_ready(&self.vm.name).await
198    }
199
200    pub async fn info(&self) -> Result<VmInfo> {
201        self.vm.info().await
202    }
203
204    pub async fn exec<I, S>(&self, command: I) -> Result<ExecOutput>
205    where
206        I: IntoIterator<Item = S>,
207        S: Into<String>,
208    {
209        let command = command.into_iter().map(Into::into).collect::<Vec<_>>();
210        self.vm.manager.exec_capture(&self.vm.name, &command).await
211    }
212
213    pub async fn exec_checked<I, S>(&self, command: I) -> Result<ExecOutput>
214    where
215        I: IntoIterator<Item = S>,
216        S: Into<String>,
217    {
218        let command = command.into_iter().map(Into::into).collect::<Vec<_>>();
219        self.vm.manager.exec_checked(&self.vm.name, &command).await
220    }
221
222    pub async fn stop(self) -> Result<Vm> {
223        self.vm.manager.stop_silent(&self.vm.name).await?;
224        Ok(self.vm)
225    }
226
227    pub async fn delete(self) -> Result<()> {
228        self.vm.manager.delete_silent(&self.vm.name).await
229    }
230}