i3status_rs/blocks/
docker.rs

1//! Local docker daemon status
2//!
3//! # Configuration
4//!
5//! Key | Values | Default
6//! ----|--------|--------
7//! `interval` | Update interval, in seconds. | `5`
8//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $running.eng(w:1) "`
9//! `socket_path` | The path to the docker socket. Supports path expansions e.g. `~`. | `"/var/run/docker.sock"`
10//!
11//! Key       | Value                          | Type   | Unit
12//! ----------|--------------------------------|--------|-----
13//! `icon`    | A static icon                  | Icon   | -
14//! `total`   | Total containers on the host   | Number | -
15//! `running` | Containers running on the host | Number | -
16//! `stopped` | Containers stopped on the host | Number | -
17//! `paused`  | Containers paused on the host  | Number | -
18//! `images`  | Total images on the host       | Number | -
19//!
20//! # Example
21//!
22//! ```toml
23//! [[block]]
24//! block = "docker"
25//! interval = 2
26//! format = " $icon $running/$total "
27//! ```
28//!
29//! # Icons Used
30//!
31//! - `docker`
32
33use super::prelude::*;
34use std::path::Path;
35use tokio::net::UnixStream;
36
37#[derive(Deserialize, Debug, SmartDefault)]
38#[serde(deny_unknown_fields, default)]
39pub struct Config {
40    #[default(5.into())]
41    pub interval: Seconds,
42    pub format: FormatConfig,
43    #[default("/var/run/docker.sock".into())]
44    pub socket_path: ShellString,
45}
46
47pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
48    let format = config.format.with_default(" $icon $running.eng(w:1) ")?;
49    let socket_path = config.socket_path.expand()?;
50
51    loop {
52        let status = Status::new(&*socket_path).await?;
53
54        let mut widget = Widget::new().with_format(format.clone());
55        widget.set_values(map! {
56            "icon" => Value::icon("docker"),
57            "total" =>   Value::number(status.total),
58            "running" => Value::number(status.running),
59            "paused" =>  Value::number(status.paused),
60            "stopped" => Value::number(status.stopped),
61            "images" =>  Value::number(status.images),
62        });
63        api.set_widget(widget)?;
64
65        select! {
66            _ = sleep(config.interval.0) => (),
67            _ = api.wait_for_update_request() => (),
68        }
69    }
70}
71
72#[derive(Deserialize, Debug)]
73struct Status {
74    #[serde(rename = "Containers")]
75    total: i64,
76    #[serde(rename = "ContainersRunning")]
77    running: i64,
78    #[serde(rename = "ContainersStopped")]
79    stopped: i64,
80    #[serde(rename = "ContainersPaused")]
81    paused: i64,
82    #[serde(rename = "Images")]
83    images: i64,
84}
85
86impl Status {
87    async fn new(socket_path: impl AsRef<Path>) -> Result<Self> {
88        let socket = UnixStream::connect(socket_path)
89            .await
90            .error("Failed to connect to socket")?;
91        let (mut request_sender, connection) = hyper::client::conn::handshake(socket)
92            .await
93            .error("Failed to create request sender")?;
94        tokio::spawn(connection);
95        let request = hyper::Request::builder()
96            .header("Host", "localhost")
97            .uri("http://api/info")
98            .method("GET")
99            .body(hyper::Body::empty())
100            .error("Failed to create request")?;
101        let response = request_sender
102            .send_request(request)
103            .await
104            .error("Failed to get response")?;
105        let bytes = hyper::body::to_bytes(response.into_body())
106            .await
107            .error("Failed to get response bytes")?;
108        serde_json::from_slice::<Self>(&bytes).error("Failed to deserialize JSON")
109    }
110}