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