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            pub fn dry_run(self, dry_run: bool) -> Self {
219                Self(self.0.field("dryRun", dry_run))
220            }
221
222            /// How many agents Kibana processes per batch for query selections.
223            pub fn batch_size(self, size: u32) -> Self {
224                Self(self.0.field("batchSize", size))
225            }
226        }
227    )*};
228}
229
230macro_rules! include_inactive_setter {
231    ($($name:ident),*) => {$(
232        impl $name<'_> {
233            /// Also selects inactive agents matched by a query.
234            pub fn include_inactive(self, include: bool) -> Self {
235                Self(self.0.field("includeInactive", include))
236            }
237        }
238    )*};
239}
240
241macro_rules! upgrade_setters {
242    ($($name:ident),*) => {$(
243        impl $name<'_> {
244            /// Upgrades even when Fleet considers the agent ineligible.
245            pub fn force(self, force: bool) -> Self {
246                Self(self.0.field("force", force))
247            }
248
249            /// Downloads agent binaries from this URI instead of the default source.
250            pub fn source_uri(self, uri: &str) -> Self {
251                Self(self.0.field("source_uri", uri))
252            }
253
254            pub fn skip_rate_limit_check(self, skip: bool) -> Self {
255                Self(self.0.field("skipRateLimitCheck", skip))
256            }
257        }
258    )*};
259}
260
261macro_rules! diagnostics_setters {
262    ($($name:ident),*) => {$(
263        impl $name<'_> {
264            pub fn additional_metrics(self, metrics: Vec<DiagnosticMetric>) -> Self {
265                Self(self.0.field("additional_metrics", metrics))
266            }
267        }
268    )*};
269}
270
271endpoint! {
272    /// `GET /api/fleet/agents`
273    FindAgents => FleetPage<Agent>
274}
275
276impl FindAgents<'_> {
277    pub fn show_inactive(self, show: bool) -> Self {
278        Self(self.0.param("showInactive", show))
279    }
280}
281
282endpoint! {
283    /// `GET /api/fleet/agents/{agentId}`
284    GetAgent => Item<Agent>
285}
286
287endpoint! {
288    /// `GET /api/fleet/agent_status`
289    AgentStatus => Value
290}
291
292impl AgentStatus<'_> {
293    pub fn policy_id(self, id: &str) -> Self {
294        Self(self.0.param("policyId", id))
295    }
296
297    pub fn kuery(self, kuery: &str) -> Self {
298        Self(self.0.param("kuery", kuery))
299    }
300}
301
302endpoint! {
303    /// `POST /api/fleet/agents/{agentId}/reassign`
304    ReassignAgent => Value
305}
306
307endpoint! {
308    /// `POST /api/fleet/agents/{agentId}/unenroll`
309    UnenrollAgent => Value
310}
311
312impl UnenrollAgent<'_> {
313    /// Removes the agent immediately instead of waiting for it to acknowledge.
314    pub fn force(self, force: bool) -> Self {
315        Self(self.0.field("force", force))
316    }
317
318    /// Revokes the agent's API keys.
319    pub fn revoke(self, revoke: bool) -> Self {
320        Self(self.0.field("revoke", revoke))
321    }
322}
323
324endpoint! {
325    /// `POST /api/fleet/agents/{agentId}/upgrade`
326    UpgradeAgent => Value
327}
328
329endpoint! {
330    /// `POST /api/fleet/agents/{agentId}/request_diagnostics`
331    RequestAgentDiagnostics => BulkActionResult
332}
333
334endpoint! {
335    /// `POST /api/fleet/agents/bulk_reassign`
336    BulkReassignAgents => BulkActionResult
337}
338
339endpoint! {
340    /// `POST /api/fleet/agents/bulk_unenroll`
341    BulkUnenrollAgents => BulkActionResult
342}
343
344impl BulkUnenrollAgents<'_> {
345    /// Removes agents immediately instead of waiting for them to acknowledge.
346    pub fn force(self, force: bool) -> Self {
347        Self(self.0.field("force", force))
348    }
349
350    /// Revokes the agents' API keys.
351    pub fn revoke(self, revoke: bool) -> Self {
352        Self(self.0.field("revoke", revoke))
353    }
354}
355
356endpoint! {
357    /// `POST /api/fleet/agents/bulk_update_agent_tags`
358    BulkUpdateAgentTags => BulkActionResult
359}
360
361impl BulkUpdateAgentTags<'_> {
362    pub fn add_tags<I: IntoIterator<Item = S>, S: Into<String>>(self, tags: I) -> Self {
363        let tags: Vec<String> = tags.into_iter().map(Into::into).collect();
364        Self(self.0.field("tagsToAdd", tags))
365    }
366
367    pub fn remove_tags<I: IntoIterator<Item = S>, S: Into<String>>(self, tags: I) -> Self {
368        let tags: Vec<String> = tags.into_iter().map(Into::into).collect();
369        Self(self.0.field("tagsToRemove", tags))
370    }
371}
372
373endpoint! {
374    /// `POST /api/fleet/agents/bulk_upgrade`
375    BulkUpgradeAgents => BulkActionResult
376}
377
378impl BulkUpgradeAgents<'_> {
379    /// An ISO 8601 time at which the rollout starts.
380    pub fn start_time(self, time: &str) -> Self {
381        Self(self.0.field("start_time", time))
382    }
383
384    /// Spreads upgrades over this many seconds.
385    pub fn rollout_duration_seconds(self, seconds: u64) -> Self {
386        Self(self.0.field("rollout_duration_seconds", seconds))
387    }
388}
389
390endpoint! {
391    /// `POST /api/fleet/agents/bulk_request_diagnostics`
392    BulkRequestAgentDiagnostics => BulkActionResult
393}
394
395page_setters!(FindAgents);
396sort_setters!(FindAgents);
397bulk_setters!(
398    BulkReassignAgents,
399    BulkUnenrollAgents,
400    BulkUpdateAgentTags,
401    BulkUpgradeAgents,
402    BulkRequestAgentDiagnostics
403);
404include_inactive_setter!(
405    BulkReassignAgents,
406    BulkUnenrollAgents,
407    BulkUpdateAgentTags,
408    BulkUpgradeAgents
409);
410upgrade_setters!(UpgradeAgent, BulkUpgradeAgents);
411diagnostics_setters!(RequestAgentDiagnostics, BulkRequestAgentDiagnostics);
412paginated!(FindAgents => FleetPage<Agent>);