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        Self::from_state(state).await
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        Self::from_state(state).await
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    async fn from_state(state: HardpassState) -> Result<Self> {
48        let instance = Self {
49            manager: Arc::new(InstanceManager::new(state)),
50        };
51        instance.manager.auto_configure_ssh_if_enabled().await;
52        Ok(instance)
53    }
54}
55
56#[derive(Debug, Clone, Default)]
57pub struct VmSpec {
58    pub name: String,
59    pub release: Option<String>,
60    pub arch: Option<GuestArch>,
61    pub accel: Option<AccelMode>,
62    pub cpus: Option<u8>,
63    pub memory_mib: Option<u32>,
64    pub disk_gib: Option<u32>,
65    pub ssh_key: Option<PathBuf>,
66    pub forwards: Vec<PortForward>,
67    pub timeout_secs: Option<u64>,
68    pub cloud_init_user_data: Option<PathBuf>,
69    pub cloud_init_network_config: Option<PathBuf>,
70}
71
72impl VmSpec {
73    pub fn new(name: impl Into<String>) -> Self {
74        Self {
75            name: name.into(),
76            ..Self::default()
77        }
78    }
79
80    pub fn release(mut self, release: impl Into<String>) -> Self {
81        self.release = Some(release.into());
82        self
83    }
84
85    pub fn arch(mut self, arch: GuestArch) -> Self {
86        self.arch = Some(arch);
87        self
88    }
89
90    pub fn accel(mut self, accel: AccelMode) -> Self {
91        self.accel = Some(accel);
92        self
93    }
94
95    pub fn cpus(mut self, cpus: u8) -> Self {
96        self.cpus = Some(cpus);
97        self
98    }
99
100    pub fn memory_mib(mut self, memory_mib: u32) -> Self {
101        self.memory_mib = Some(memory_mib);
102        self
103    }
104
105    pub fn disk_gib(mut self, disk_gib: u32) -> Self {
106        self.disk_gib = Some(disk_gib);
107        self
108    }
109
110    pub fn ssh_key(mut self, ssh_key: impl AsRef<Path>) -> Self {
111        self.ssh_key = Some(ssh_key.as_ref().to_path_buf());
112        self
113    }
114
115    pub fn forward(mut self, host: u16, guest: u16) -> Self {
116        self.forwards.push(PortForward { host, guest });
117        self
118    }
119
120    pub fn timeout_secs(mut self, timeout_secs: u64) -> Self {
121        self.timeout_secs = Some(timeout_secs);
122        self
123    }
124
125    pub fn cloud_init_user_data(mut self, path: impl AsRef<Path>) -> Self {
126        self.cloud_init_user_data = Some(path.as_ref().to_path_buf());
127        self
128    }
129
130    pub fn cloud_init_network_config(mut self, path: impl AsRef<Path>) -> Self {
131        self.cloud_init_network_config = Some(path.as_ref().to_path_buf());
132        self
133    }
134
135    fn into_create_args(self) -> CreateArgs {
136        CreateArgs {
137            name: self.name,
138            release: self.release,
139            arch: self.arch,
140            accel: self.accel,
141            cpus: self.cpus,
142            memory_mib: self.memory_mib,
143            disk_gib: self.disk_gib,
144            ssh_key: self.ssh_key.map(|path| path.display().to_string()),
145            forwards: self
146                .forwards
147                .into_iter()
148                .map(|forward| (forward.host, forward.guest))
149                .collect(),
150            timeout_secs: self.timeout_secs,
151            cloud_init_user_data: self
152                .cloud_init_user_data
153                .map(|path| path.display().to_string()),
154            cloud_init_network_config: self
155                .cloud_init_network_config
156                .map(|path| path.display().to_string()),
157        }
158    }
159}
160
161pub struct Vm {
162    manager: Arc<InstanceManager>,
163    name: String,
164}
165
166impl Vm {
167    pub fn name(&self) -> &str {
168        &self.name
169    }
170
171    pub async fn start(&self) -> Result<()> {
172        self.manager.start_silent(&self.name).await?;
173        Ok(())
174    }
175
176    pub async fn info(&self) -> Result<VmInfo> {
177        self.manager.vm_info(&self.name).await
178    }
179
180    pub async fn status(&self) -> Result<InstanceStatus> {
181        self.manager.status(&self.name).await
182    }
183
184    pub async fn wait_for_ssh(&self) -> Result<VmInfo> {
185        self.manager.wait_for_ssh_ready(&self.name).await
186    }
187
188    pub async fn exec<I, S>(&self, command: I) -> Result<ExecOutput>
189    where
190        I: IntoIterator<Item = S>,
191        S: Into<String>,
192    {
193        let command = command.into_iter().map(Into::into).collect::<Vec<_>>();
194        self.manager.exec_capture(&self.name, &command).await
195    }
196
197    pub async fn exec_checked<I, S>(&self, command: I) -> Result<ExecOutput>
198    where
199        I: IntoIterator<Item = S>,
200        S: Into<String>,
201    {
202        let command = command.into_iter().map(Into::into).collect::<Vec<_>>();
203        self.manager.exec_checked(&self.name, &command).await
204    }
205
206    pub async fn stop(&self) -> Result<()> {
207        self.manager.stop_silent(&self.name).await
208    }
209
210    pub async fn delete(&self) -> Result<()> {
211        self.manager.delete_silent(&self.name).await
212    }
213}