1use serde::{Deserialize, Serialize};
12
13use crate::error::{Error, Result};
14use crate::operations::{operation_from_envelope, Operation};
15use crate::transport::{
16 resource_error_or, sync_metadata, Client, Method, RecursionQuery, WithEtag,
17};
18
19#[derive(Debug, Clone, Deserialize)]
24pub struct Instance {
25 pub name: String,
26 pub status: String,
27 pub status_code: u16,
28 #[serde(rename = "type")]
29 pub instance_type: String,
30 pub architecture: String,
31 pub created_at: String,
32 pub last_used_at: String,
33 pub location: String,
34 pub project: String,
35 #[serde(default)]
36 pub config: serde_json::Value,
37 #[serde(default)]
38 pub devices: serde_json::Value,
39 #[serde(default)]
40 pub profiles: Vec<String>,
41}
42
43#[derive(Debug, Clone, Serialize)]
48pub struct CreateInstanceParams {
49 pub name: String,
50 #[serde(rename = "type")]
51 pub instance_type: String,
52 pub source: serde_json::Value,
53}
54
55impl Client {
56 pub async fn list_instances(&self, recursion: bool) -> Result<Vec<serde_json::Value>> {
61 let recursion_query = RecursionQuery::new(recursion);
62 let envelope = self
63 .request(
64 Method::Get,
65 "/1.0/instances",
66 &recursion_query.as_query(),
67 None,
68 None,
69 )
70 .await?;
71 Ok(serde_json::from_value(sync_metadata(envelope, "list")?)?)
72 }
73
74 pub async fn get_instance(&self, name: &str) -> Result<WithEtag<Instance>> {
77 let path = format!("/1.0/instances/{name}");
78 let envelope = self
79 .request(Method::Get, &path, &[], None, None)
80 .await
81 .map_err(|err| resource_error_or(err, name))?;
82 match envelope {
83 crate::transport::IncusEnvelope::Sync { metadata, etag } => Ok(WithEtag {
84 value: serde_json::from_value(metadata)?,
85 etag,
86 }),
87 other => Err(Error::InvalidResponse(format!(
88 "expected a sync instance response, got {other:?}"
89 ))),
90 }
91 }
92
93 pub async fn create_instance(&self, params: &CreateInstanceParams) -> Result<Operation> {
96 let body = serde_json::to_value(params)?;
97 let envelope = self
98 .request(Method::Post, "/1.0/instances", &[], Some(&body), None)
99 .await?;
100 operation_from_envelope(envelope)
101 }
102
103 pub async fn update_instance(
107 &self,
108 name: &str,
109 new_definition: &serde_json::Value,
110 etag: Option<&str>,
111 ) -> Result<Operation> {
112 let path = format!("/1.0/instances/{name}");
113 let envelope = self
114 .request(Method::Put, &path, &[], Some(new_definition), etag)
115 .await
116 .map_err(|err| resource_error_or(err, name))?;
117 operation_from_envelope(envelope)
118 }
119
120 pub async fn update_instance_guarded(
129 &self,
130 fetched: &WithEtag<Instance>,
131 new_definition: &serde_json::Value,
132 ) -> Result<Operation> {
133 self.update_instance(&fetched.value().name, new_definition, fetched.etag())
134 .await
135 }
136
137 pub async fn patch_instance(
140 &self,
141 name: &str,
142 patch: &serde_json::Value,
143 etag: Option<&str>,
144 ) -> Result<Operation> {
145 let path = format!("/1.0/instances/{name}");
146 let envelope = self
147 .request(Method::Patch, &path, &[], Some(patch), etag)
148 .await
149 .map_err(|err| resource_error_or(err, name))?;
150 operation_from_envelope(envelope)
151 }
152
153 pub async fn patch_instance_guarded(
158 &self,
159 fetched: &WithEtag<Instance>,
160 patch: &serde_json::Value,
161 ) -> Result<Operation> {
162 self.patch_instance(&fetched.value().name, patch, fetched.etag())
163 .await
164 }
165
166 pub async fn delete_instance(&self, name: &str) -> Result<Operation> {
167 let path = format!("/1.0/instances/{name}");
168 let envelope = self
169 .request(Method::Delete, &path, &[], None, None)
170 .await
171 .map_err(|err| resource_error_or(err, name))?;
172 operation_from_envelope(envelope)
173 }
174
175 async fn set_state(&self, name: &str, action: &str) -> Result<Operation> {
176 let path = format!("/1.0/instances/{name}/state");
177 let body = serde_json::json!({ "action": action });
178 let envelope = self
179 .request(Method::Put, &path, &[], Some(&body), None)
180 .await?;
181 operation_from_envelope(envelope)
182 }
183
184 pub async fn start_instance(&self, name: &str) -> Result<Operation> {
185 self.set_state(name, "start").await
186 }
187
188 pub async fn stop_instance(&self, name: &str) -> Result<Operation> {
189 self.set_state(name, "stop").await
190 }
191
192 pub async fn restart_instance(&self, name: &str) -> Result<Operation> {
193 self.set_state(name, "restart").await
194 }
195
196 pub async fn pause_instance(&self, name: &str) -> Result<Operation> {
197 self.set_state(name, "freeze").await
198 }
199
200 pub async fn list_snapshots(
201 &self,
202 instance_name: &str,
203 recursion: bool,
204 ) -> Result<Vec<serde_json::Value>> {
205 let recursion_query = RecursionQuery::new(recursion);
206 let path = format!("/1.0/instances/{instance_name}/snapshots");
207 let envelope = self
208 .request(Method::Get, &path, &recursion_query.as_query(), None, None)
209 .await?;
210 Ok(serde_json::from_value(sync_metadata(envelope, "list")?)?)
211 }
212
213 pub async fn create_snapshot(
214 &self,
215 instance_name: &str,
216 snapshot_name: &str,
217 ) -> Result<Operation> {
218 let path = format!("/1.0/instances/{instance_name}/snapshots");
219 let body = serde_json::json!({ "name": snapshot_name });
220 let envelope = self
221 .request(Method::Post, &path, &[], Some(&body), None)
222 .await?;
223 operation_from_envelope(envelope)
224 }
225
226 pub async fn delete_snapshot(
227 &self,
228 instance_name: &str,
229 snapshot_name: &str,
230 ) -> Result<Operation> {
231 let path = format!("/1.0/instances/{instance_name}/snapshots/{snapshot_name}");
232 let envelope = self
233 .request(Method::Delete, &path, &[], None, None)
234 .await
235 .map_err(|err| resource_error_or(err, snapshot_name))?;
236 operation_from_envelope(envelope)
237 }
238}
239
240#[cfg(test)]
241#[path = "instances_tests.rs"]
242mod tests;