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}