Skip to main content

security_client_rs/fleet/
wait.rs

1//! Waiting for asynchronous Fleet work. Each wait is bounded by
2//! [`PollOptions`]; see [`crate::poll`] for the deadline rules.
3use super::{AgentActionStatus, AgentUpload, Fleet, agents::Agent};
4use crate::{
5    Result,
6    poll::{self, PollOptions, WaitOutcome},
7};
8
9impl Fleet<'_> {
10    /// Waits until an agent action finishes.
11    ///
12    /// Kibana has no lookup by action ID, so each check searches progressively
13    /// larger history windows, up to 10,000 action documents, within the
14    /// deadline. Bulk actions can occupy multiple documents. An action that was
15    /// seen and then can no longer be found, for example because newer actions
16    /// pushed it out of that window, ends the
17    /// wait as [`WaitOutcome::Vanished`] with its last state.
18    ///
19    /// A finished action can contain failed agents. Unknown action statuses also
20    /// end the wait for inspection; check the status, failure count and errors
21    /// on the returned [`AgentActionStatus`].
22    pub async fn wait_for_action(
23        &self,
24        action_id: &str,
25        options: PollOptions,
26    ) -> Result<WaitOutcome<AgentActionStatus>> {
27        poll::wait(
28            options,
29            || self.find_action(action_id),
30            AgentActionStatus::is_finished,
31        )
32        .await
33    }
34
35    async fn find_action(&self, action_id: &str) -> Result<Option<AgentActionStatus>> {
36        for size in [100, 1_000, 10_000] {
37            let actions = self
38                .agent_action_status()
39                .page(0)
40                .per_page(size)
41                .send()
42                .await?
43                .json()
44                .await?;
45            if let Some(action) = actions.items.into_iter().find(|a| a.action_id == action_id) {
46                return Ok(Some(action));
47            }
48        }
49        Ok(None)
50    }
51
52    /// Waits until the upload created by a diagnostics action finishes.
53    /// Download it with [`download_agent_file`](Self::download_agent_file) once
54    /// it is [`Ready`](super::UploadStatus::Ready). An upload that was seen and
55    /// then disappears ends the wait as [`WaitOutcome::Vanished`].
56    pub async fn wait_for_upload(
57        &self,
58        agent_id: &str,
59        action_id: &str,
60        options: PollOptions,
61    ) -> Result<WaitOutcome<AgentUpload>> {
62        poll::wait(
63            options,
64            || async {
65                let uploads = self
66                    .list_agent_uploads(agent_id)
67                    .send()
68                    .await?
69                    .json()
70                    .await?;
71                Ok(uploads.items.into_iter().find(|u| u.action_id == action_id))
72            },
73            AgentUpload::is_finished,
74        )
75        .await
76    }
77
78    /// Waits until the agent is online and reports `policy_id` at `revision` or
79    /// later. An agent that has not reported a revision has not acknowledged it.
80    pub async fn wait_for_agent_policy(
81        &self,
82        agent_id: &str,
83        policy_id: &str,
84        revision: u64,
85        options: PollOptions,
86    ) -> Result<WaitOutcome<Agent>> {
87        poll::wait(
88            options,
89            || async {
90                Ok(Some(
91                    self.get_agent(agent_id).send().await?.json().await?.item,
92                ))
93            },
94            |agent| {
95                agent.policy_id.as_deref() == Some(policy_id)
96                    && agent.status.as_deref() == Some("online")
97                    && agent
98                        .policy_revision
99                        .is_some_and(|current| current >= revision)
100            },
101        )
102        .await
103    }
104}