Skip to main content

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` (`$icon`)
32
33use super::prelude::*;
34
35#[derive(Deserialize, Debug, SmartDefault)]
36#[serde(deny_unknown_fields, default)]
37pub struct Config {
38    #[default(5.into())]
39    pub interval: Seconds,
40    pub format: FormatConfig,
41    #[default("/var/run/docker.sock".into())]
42    pub socket_path: ShellString,
43}
44
45pub(crate) fn prepare(config: &Config) -> Result<Arc<BlockPlan>> {
46    BlockPlan::new(vec![
47        OutputPlan::new(
48            "main",
49            config.format.with_default(" $icon $running.eng(w:1) ")?,
50        )
51        .icon("icon", IconChoices::one(icons::DOCKER)),
52    ])
53}
54
55pub(crate) async fn run(config: &Config, api: &CommonApi, plan: &Arc<BlockPlan>) -> Result<()> {
56    let output_main = plan.output("main")?;
57    let socket_path = config.socket_path.expand()?;
58
59    let client = reqwest::Client::builder()
60        .unix_socket(&*socket_path)
61        .build()
62        .unwrap();
63
64    loop {
65        let status: Status = client
66            .get("http://api/info")
67            .send()
68            .await
69            .error("Failed to get response")?
70            .json()
71            .await
72            .error("Failed to deserialize JSON")?;
73
74        let mut widget = output_main.new_widget();
75        widget.set_values(map! {
76            "icon" => Value::icon(icons::DOCKER),
77            "total" =>   Value::number(status.total),
78            "running" => Value::number(status.running),
79            "paused" =>  Value::number(status.paused),
80            "stopped" => Value::number(status.stopped),
81            "images" =>  Value::number(status.images),
82        });
83        api.set_widget(widget)?;
84
85        select! {
86            _ = sleep(config.interval.0) => (),
87            _ = api.wait_for_update_request() => (),
88        }
89    }
90}
91
92#[derive(Deserialize, Debug)]
93struct Status {
94    #[serde(rename = "Containers")]
95    total: i64,
96    #[serde(rename = "ContainersRunning")]
97    running: i64,
98    #[serde(rename = "ContainersStopped")]
99    stopped: i64,
100    #[serde(rename = "ContainersPaused")]
101    paused: i64,
102    #[serde(rename = "Images")]
103    images: i64,
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn plan_declares_main_output_with_docker_icon() {
112        let plan = prepare(&Config::default()).unwrap();
113        let declared: Vec<_> = plan.outputs().map(|o| o.id()).collect();
114        assert_eq!(declared, ["main"]);
115        let output = plan.output("main").unwrap();
116        assert_eq!(output.single_icon("icon").unwrap(), "docker");
117    }
118
119    #[test]
120    fn custom_format_is_respected() {
121        let config = Config {
122            format: " $running ".parse().unwrap(),
123            ..Config::default()
124        };
125        let plan = prepare(&config).unwrap();
126        let output = plan.output("main").unwrap();
127        assert!(output.format().contains_key("running"));
128        assert!(!output.format().contains_key("icon"));
129    }
130}