Skip to main content

security_client_rs/fleet/
agents.rs

1//! Enrolled agents and the actions submitted to them.
2use serde::{Deserialize, Serialize};
3use serde_json::{Map, Value, json};
4
5use super::{Fleet, FleetPage, Item};
6use crate::{Scope, http::Method, pagination::paginated, request::endpoint};
7
8#[derive(Clone, Debug, Deserialize, Serialize)]
9#[non_exhaustive]
10pub struct Agent {
11    pub id: String,
12    #[serde(default)]
13    pub policy_id: Option<String>,
14    /// The revision of `policy_id` the agent last acknowledged. Absent until it
15    /// reports one.
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub policy_revision: Option<u64>,
18    #[serde(default)]
19    pub active: Option<bool>,
20    #[serde(default)]
21    pub status: Option<String>,
22    #[serde(default)]
23    pub local_metadata: Value,
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub last_checkin: Option<String>,
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub tags: Option<Vec<String>>,
28    #[serde(flatten)]
29    pub extra: Map<String, Value>,
30}
31
32/// An explicit ID set or a Fleet KQL query. A query can select many agents.
33#[derive(Clone, Debug, Serialize)]
34#[serde(untagged)]
35pub enum AgentSelection {
36    Ids(Vec<String>),
37    Query(String),
38}
39
40/// Submission does not prove completion. Follow an action ID through
41/// [`Fleet::agent_action_status`] or [`Fleet::wait_for_action`].
42#[derive(Clone, Debug, Deserialize, Serialize)]
43#[serde(untagged)]
44#[non_exhaustive]
45pub enum BulkActionResult {
46    Action {
47        #[serde(rename = "actionId")]
48        action_id: String,
49    },
50    /// The number of selected agents. Eligibility is not checked for every agent.
51    DryRun { count: u64 },
52}
53
54#[derive(Clone, Copy, Debug, Serialize)]
55#[non_exhaustive]
56pub enum DiagnosticMetric {
57    #[serde(rename = "CPU")]
58    Cpu,
59}
60
61impl<'a> Fleet<'a> {
62    pub fn find_agents(&self) -> FindAgents<'a> {
63        FindAgents(
64            self.0
65                .request(Method::GET, Scope::Space, &["api", "fleet", "agents"]),
66        )
67    }
68
69    pub fn get_agent(&self, id: &str) -> GetAgent<'a> {
70        GetAgent(
71            self.0
72                .request(Method::GET, Scope::Space, &["api", "fleet", "agents", id]),
73        )
74    }
75
76    /// Agent counts by status.
77    pub fn agent_status(&self) -> AgentStatus<'a> {
78        AgentStatus(
79            self.0
80                .request(Method::GET, Scope::Space, &["api", "fleet", "agent_status"]),
81        )
82    }
83
84    pub fn reassign_agent(&self, id: &str, policy_id: &str) -> ReassignAgent<'a> {
85        ReassignAgent(
86            self.0
87                .request(
88                    Method::POST,
89                    Scope::Space,
90                    &["api", "fleet", "agents", id, "reassign"],
91                )
92                .nonempty("policy_id", policy_id)
93                .field("policy_id", policy_id),
94        )
95    }
96
97    pub fn unenroll_agent(&self, id: &str) -> UnenrollAgent<'a> {
98        UnenrollAgent(
99            self.0
100                .request(
101                    Method::POST,
102                    Scope::Space,
103                    &["api", "fleet", "agents", id, "unenroll"],
104                )
105                .json(&json!({})),
106        )
107    }
108
109    /// Schedules an upgrade; the acknowledgment is not proof of completion.
110    /// Container agents cannot perform Fleet-managed binary upgrades.
111    pub fn upgrade_agent(&self, id: &str, version: &str) -> UpgradeAgent<'a> {
112        UpgradeAgent(
113            self.0
114                .request(
115                    Method::POST,
116                    Scope::Space,
117                    &["api", "fleet", "agents", id, "upgrade"],
118                )
119                .nonempty("version", version)
120                .field("version", version),
121        )
122    }
123
124    /// Correlate the returned action ID with [`list_agent_uploads`](Self::list_agent_uploads)
125    /// or [`wait_for_upload`](Self::wait_for_upload).
126    pub fn request_agent_diagnostics(&self, id: &str) -> RequestAgentDiagnostics<'a> {
127        RequestAgentDiagnostics(
128            self.0
129                .request(
130                    Method::POST,
131                    Scope::Space,
132                    &["api", "fleet", "agents", id, "request_diagnostics"],
133                )
134                .json(&json!({})),
135        )
136    }
137
138    pub fn bulk_reassign_agents(
139        &self,
140        agents: AgentSelection,
141        policy_id: &str,
142    ) -> BulkReassignAgents<'a> {
143        BulkReassignAgents(
144            self.0
145                .request(
146                    Method::POST,
147                    Scope::Space,
148                    &["api", "fleet", "agents", "bulk_reassign"],
149                )
150                .field("agents", agents)
151                .nonempty("policy_id", policy_id)
152                .field("policy_id", policy_id),
153        )
154    }
155
156    pub fn bulk_unenroll_agents(&self, agents: AgentSelection) -> BulkUnenrollAgents<'a> {
157        BulkUnenrollAgents(
158            self.0
159                .request(
160                    Method::POST,
161                    Scope::Space,
162                    &["api", "fleet", "agents", "bulk_unenroll"],
163                )
164                .field("agents", agents),
165        )
166    }
167
168    pub fn bulk_update_agent_tags(&self, agents: AgentSelection) -> BulkUpdateAgentTags<'a> {
169        BulkUpdateAgentTags(
170            self.0
171                .request(
172                    Method::POST,
173                    Scope::Space,
174                    &["api", "fleet", "agents", "bulk_update_agent_tags"],
175                )
176                .field("agents", agents),
177        )
178    }
179
180    pub fn bulk_upgrade_agents(
181        &self,
182        agents: AgentSelection,
183        version: &str,
184    ) -> BulkUpgradeAgents<'a> {
185        BulkUpgradeAgents(
186            self.0
187                .request(
188                    Method::POST,
189                    Scope::Space,
190                    &["api", "fleet", "agents", "bulk_upgrade"],
191                )
192                .field("agents", agents)
193                .nonempty("version", version)
194                .field("version", version),
195        )
196    }
197
198    pub fn bulk_request_agent_diagnostics(
199        &self,
200        agents: AgentSelection,
201    ) -> BulkRequestAgentDiagnostics<'a> {
202        BulkRequestAgentDiagnostics(
203            self.0
204                .request(
205                    Method::POST,
206                    Scope::Space,
207                    &["api", "fleet", "agents", "bulk_request_diagnostics"],
208                )
209                .field("agents", agents),
210        )
211    }
212}
213
214macro_rules! bulk_setters {
215    ($($name:ident),*) => {$(
216        impl $name<'_> {
217            /// Returns the number of selected agents instead of creating an action.
218            /// Kibana 9.3 and later; earlier versions reject the field.
219            pub fn dry_run(self, dry_run: bool) -> Self {
220                Self(self.0.field("dryRun", dry_run))
221            }
222
223            /// How many agents Kibana processes per batch for query selections.
224            /// Kibana 9.0 and later; 8.19 accepts it only for tag updates and
225            /// diagnostics requests.
226            pub fn batch_size(self, size: u32) -> Self {
227                Self(self.0.field("batchSize", size))
228            }
229        }
230    )*};
231}
232
233macro_rules! include_inactive_setter {
234    ($($name:ident),*) => {$(
235        impl $name<'_> {
236            /// Also selects inactive agents matched by a query.
237            pub fn include_inactive(self, include: bool) -> Self {
238                Self(self.0.field("includeInactive", include))
239            }
240        }
241    )*};
242}
243
244macro_rules! upgrade_setters {
245    ($($name:ident),*) => {$(
246        impl $name<'_> {
247            /// Upgrades even when Fleet considers the agent ineligible.
248            pub fn force(self, force: bool) -> Self {
249                Self(self.0.field("force", force))
250            }
251
252            /// Downloads agent binaries from this URI instead of the default source.
253            pub fn source_uri(self, uri: &str) -> Self {
254                Self(self.0.field("source_uri", uri))
255            }
256
257            pub fn skip_rate_limit_check(self, skip: bool) -> Self {
258                Self(self.0.field("skipRateLimitCheck", skip))
259            }
260        }
261    )*};
262}
263
264macro_rules! diagnostics_setters {
265    ($($name:ident),*) => {$(
266        impl $name<'_> {
267            pub fn additional_metrics(self, metrics: Vec<DiagnosticMetric>) -> Self {
268                Self(self.0.field("additional_metrics", metrics))
269            }
270        }
271    )*};
272}
273
274endpoint! {
275    /// `GET /api/fleet/agents`
276    FindAgents => FleetPage<Agent>
277}
278
279impl FindAgents<'_> {
280    pub fn show_inactive(self, show: bool) -> Self {
281        Self(self.0.param("showInactive", show))
282    }
283}
284
285endpoint! {
286    /// `GET /api/fleet/agents/{agentId}`
287    GetAgent => Item<Agent>
288}
289
290endpoint! {
291    /// `GET /api/fleet/agent_status`
292    AgentStatus => Value
293}
294
295impl AgentStatus<'_> {
296    pub fn policy_id(self, id: &str) -> Self {
297        Self(self.0.param("policyId", id))
298    }
299
300    pub fn kuery(self, kuery: &str) -> Self {
301        Self(self.0.param("kuery", kuery))
302    }
303}
304
305endpoint! {
306    /// `POST /api/fleet/agents/{agentId}/reassign`
307    ReassignAgent => Value
308}
309
310endpoint! {
311    /// `POST /api/fleet/agents/{agentId}/unenroll`
312    UnenrollAgent => Value
313}
314
315impl UnenrollAgent<'_> {
316    /// Removes the agent immediately instead of waiting for it to acknowledge.
317    pub fn force(self, force: bool) -> Self {
318        Self(self.0.field("force", force))
319    }
320
321    /// Revokes the agent's API keys.
322    pub fn revoke(self, revoke: bool) -> Self {
323        Self(self.0.field("revoke", revoke))
324    }
325}
326
327endpoint! {
328    /// `POST /api/fleet/agents/{agentId}/upgrade`
329    UpgradeAgent => Value
330}
331
332endpoint! {
333    /// `POST /api/fleet/agents/{agentId}/request_diagnostics`
334    RequestAgentDiagnostics => BulkActionResult
335}
336
337endpoint! {
338    /// `POST /api/fleet/agents/bulk_reassign`
339    BulkReassignAgents => BulkActionResult
340}
341
342endpoint! {
343    /// `POST /api/fleet/agents/bulk_unenroll`
344    BulkUnenrollAgents => BulkActionResult
345}
346
347impl BulkUnenrollAgents<'_> {
348    /// Removes agents immediately instead of waiting for them to acknowledge.
349    pub fn force(self, force: bool) -> Self {
350        Self(self.0.field("force", force))
351    }
352
353    /// Revokes the agents' API keys.
354    pub fn revoke(self, revoke: bool) -> Self {
355        Self(self.0.field("revoke", revoke))
356    }
357}
358
359endpoint! {
360    /// `POST /api/fleet/agents/bulk_update_agent_tags`
361    BulkUpdateAgentTags => BulkActionResult
362}
363
364impl BulkUpdateAgentTags<'_> {
365    pub fn add_tags<I: IntoIterator<Item = S>, S: Into<String>>(self, tags: I) -> Self {
366        let tags: Vec<String> = tags.into_iter().map(Into::into).collect();
367        Self(self.0.field("tagsToAdd", tags))
368    }
369
370    pub fn remove_tags<I: IntoIterator<Item = S>, S: Into<String>>(self, tags: I) -> Self {
371        let tags: Vec<String> = tags.into_iter().map(Into::into).collect();
372        Self(self.0.field("tagsToRemove", tags))
373    }
374}
375
376endpoint! {
377    /// `POST /api/fleet/agents/bulk_upgrade`
378    BulkUpgradeAgents => BulkActionResult
379}
380
381impl BulkUpgradeAgents<'_> {
382    /// An ISO 8601 time at which the rollout starts.
383    pub fn start_time(self, time: &str) -> Self {
384        Self(self.0.field("start_time", time))
385    }
386
387    /// Spreads upgrades over this many seconds.
388    pub fn rollout_duration_seconds(self, seconds: u64) -> Self {
389        Self(self.0.field("rollout_duration_seconds", seconds))
390    }
391}
392
393endpoint! {
394    /// `POST /api/fleet/agents/bulk_request_diagnostics`
395    BulkRequestAgentDiagnostics => BulkActionResult
396}
397
398page_setters!(FindAgents);
399sort_setters!(FindAgents);
400bulk_setters!(
401    BulkReassignAgents,
402    BulkUnenrollAgents,
403    BulkUpdateAgentTags,
404    BulkUpgradeAgents,
405    BulkRequestAgentDiagnostics
406);
407include_inactive_setter!(
408    BulkReassignAgents,
409    BulkUnenrollAgents,
410    BulkUpdateAgentTags,
411    BulkUpgradeAgents
412);
413upgrade_setters!(UpgradeAgent, BulkUpgradeAgents);
414diagnostics_setters!(RequestAgentDiagnostics, BulkRequestAgentDiagnostics);
415paginated!(FindAgents => FleetPage<Agent>);