Skip to main content

incus_client/resources/
networks.rs

1//! Network CRUD.
2
3use serde::Deserialize;
4
5use crate::error::{Error, Result};
6use crate::transport::{
7    resource_error_or, sync_metadata, Client, Method, RecursionQuery, WithEtag,
8};
9
10#[derive(Debug, Clone, Deserialize)]
11pub struct Network {
12    pub name: String,
13    #[serde(rename = "type")]
14    pub network_type: String,
15    pub managed: bool,
16    pub status: String,
17    #[serde(default)]
18    pub config: serde_json::Value,
19}
20
21impl Client {
22    pub async fn list_networks(&self, recursion: bool) -> Result<Vec<serde_json::Value>> {
23        let recursion_query = RecursionQuery::new(recursion);
24        let envelope = self
25            .request(
26                Method::Get,
27                "/1.0/networks",
28                &recursion_query.as_query(),
29                None,
30                None,
31            )
32            .await?;
33        Ok(serde_json::from_value(sync_metadata(envelope, "list")?)?)
34    }
35
36    /// Fetches one network by name, along with its ETag for use as a later
37    /// `If-Match` precondition.
38    pub async fn get_network(&self, name: &str) -> Result<WithEtag<Network>> {
39        let path = format!("/1.0/networks/{name}");
40        let envelope = self
41            .request(Method::Get, &path, &[], None, None)
42            .await
43            .map_err(|err| resource_error_or(err, name))?;
44        match envelope {
45            crate::transport::IncusEnvelope::Sync { metadata, etag } => Ok(WithEtag {
46                value: serde_json::from_value(metadata)?,
47                etag,
48            }),
49            other => Err(Error::InvalidResponse(format!(
50                "expected a sync network response, got {other:?}"
51            ))),
52        }
53    }
54
55    /// Creates a network. Synchronous: the network exists by the time this
56    /// returns, with no operation to wait on. Verified against
57    /// `cmd/incusd/networks.go`'s `networksPost` on the `lxc/incus` `main`
58    /// branch, which always returns `response.SyncResponseLocation` -
59    /// there is no code path that returns an async operation response, for
60    /// any network type/backend.
61    pub async fn create_network(&self, params: &serde_json::Value) -> Result<()> {
62        self.request(Method::Post, "/1.0/networks", &[], Some(params), None)
63            .await?;
64        Ok(())
65    }
66
67    /// Full replacement update (PUT). `etag`, if provided, is sent as
68    /// `If-Match` for optimistic concurrency; a stale ETag surfaces as
69    /// `Error::PreconditionFailed`, not the generic `Error::Api`.
70    ///
71    /// Synchronous, like [`Client::create_network`] - verified against
72    /// `cmd/incusd/networks.go`'s `doNetworkUpdate`, which always returns
73    /// `response.EmptySyncResponse`.
74    pub async fn update_network(
75        &self,
76        name: &str,
77        new_definition: &serde_json::Value,
78        etag: Option<&str>,
79    ) -> Result<()> {
80        let path = format!("/1.0/networks/{name}");
81        self.request(Method::Put, &path, &[], Some(new_definition), etag)
82            .await
83            .map_err(|err| resource_error_or(err, name))?;
84        Ok(())
85    }
86
87    /// Same as [`Client::update_network`], but takes the `WithEtag` from a
88    /// prior [`Client::get_network`] 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_network_guarded(
92        &self,
93        fetched: &WithEtag<Network>,
94        new_definition: &serde_json::Value,
95    ) -> Result<()> {
96        self.update_network(&fetched.value().name, new_definition, fetched.etag())
97            .await
98    }
99
100    /// Synchronous - verified against `cmd/incusd/networks.go`'s
101    /// `networkDelete`, which always returns `response.EmptySyncResponse`.
102    pub async fn delete_network(&self, name: &str) -> Result<()> {
103        let path = format!("/1.0/networks/{name}");
104        self.request(Method::Delete, &path, &[], None, None)
105            .await
106            .map_err(|err| resource_error_or(err, name))?;
107        Ok(())
108    }
109}
110
111#[cfg(test)]
112#[path = "networks_tests.rs"]
113mod tests;