Skip to main content

incus_client/resources/
images.rs

1//! Image CRUD.
2
3use serde::Deserialize;
4
5use crate::error::{Error, Result};
6use crate::operations::{operation_from_envelope, Operation};
7use crate::transport::{
8    resource_error_or, sync_metadata, Client, Method, RecursionQuery, WithEtag,
9};
10
11#[derive(Debug, Clone, Deserialize)]
12pub struct Image {
13    pub fingerprint: String,
14    pub public: bool,
15    pub filename: String,
16    pub size: i64,
17    pub architecture: String,
18    pub created_at: String,
19    pub uploaded_at: String,
20    #[serde(default)]
21    pub properties: serde_json::Value,
22}
23
24impl Client {
25    /// `recursion = true` fetches every image's full object in one call;
26    /// `recursion = false` returns lightweight fingerprint/URL references.
27    pub async fn list_images(&self, recursion: bool) -> Result<Vec<serde_json::Value>> {
28        let recursion_query = RecursionQuery::new(recursion);
29        let envelope = self
30            .request(
31                Method::Get,
32                "/1.0/images",
33                &recursion_query.as_query(),
34                None,
35                None,
36            )
37            .await?;
38        Ok(serde_json::from_value(sync_metadata(envelope, "list")?)?)
39    }
40
41    /// Fetches one image by fingerprint, along with its ETag for use as a
42    /// later `If-Match` precondition.
43    pub async fn get_image(&self, fingerprint: &str) -> Result<WithEtag<Image>> {
44        let path = format!("/1.0/images/{fingerprint}");
45        let envelope = self
46            .request(Method::Get, &path, &[], None, None)
47            .await
48            .map_err(|err| resource_error_or(err, fingerprint))?;
49        match envelope {
50            crate::transport::IncusEnvelope::Sync { metadata, etag } => Ok(WithEtag {
51                value: serde_json::from_value(metadata)?,
52                etag,
53            }),
54            other => Err(Error::InvalidResponse(format!(
55                "expected a sync image response, got {other:?}"
56            ))),
57        }
58    }
59
60    /// Always async: image import/creation is documented as a long-running
61    /// operation (fetching/unpacking a source, which can be a remote URL or
62    /// a large upload).
63    pub async fn create_image(&self, params: &serde_json::Value) -> Result<Operation> {
64        let envelope = self
65            .request(Method::Post, "/1.0/images", &[], Some(params), None)
66            .await?;
67        operation_from_envelope(envelope)
68    }
69
70    /// Full replacement update (PUT). `etag`, if provided, is sent as
71    /// `If-Match` for optimistic concurrency; a stale ETag surfaces as
72    /// `Error::PreconditionFailed`, not the generic `Error::Api`.
73    pub async fn update_image(
74        &self,
75        fingerprint: &str,
76        new_definition: &serde_json::Value,
77        etag: Option<&str>,
78    ) -> Result<Operation> {
79        let path = format!("/1.0/images/{fingerprint}");
80        let envelope = self
81            .request(Method::Put, &path, &[], Some(new_definition), etag)
82            .await
83            .map_err(|err| resource_error_or(err, fingerprint))?;
84        operation_from_envelope(envelope)
85    }
86
87    /// Same as [`Client::update_image`], but takes the `WithEtag` from a
88    /// prior [`Client::get_image`] call directly instead of a bare
89    /// `etag: Option<&str>` - see `instances::Client::update_instance_guarded`'s
90    /// doc comment for why this exists alongside the raw-`etag` version.
91    pub async fn update_image_guarded(
92        &self,
93        fetched: &WithEtag<Image>,
94        new_definition: &serde_json::Value,
95    ) -> Result<Operation> {
96        self.update_image(&fetched.value().fingerprint, new_definition, fetched.etag())
97            .await
98    }
99
100    pub async fn delete_image(&self, fingerprint: &str) -> Result<Operation> {
101        let path = format!("/1.0/images/{fingerprint}");
102        let envelope = self
103            .request(Method::Delete, &path, &[], None, None)
104            .await
105            .map_err(|err| resource_error_or(err, fingerprint))?;
106        operation_from_envelope(envelope)
107    }
108}
109
110#[cfg(test)]
111#[path = "images_tests.rs"]
112mod tests;