Skip to main content

omni_dev/cli/
datadog.rs

1//! Datadog CLI commands (read-only).
2
3pub(crate) mod auth;
4pub(crate) mod dashboard;
5pub(crate) mod downtime;
6pub(crate) mod events;
7pub(crate) mod format;
8pub(crate) mod helpers;
9pub(crate) mod hosts;
10pub(crate) mod logs;
11pub(crate) mod metrics;
12pub(crate) mod monitor;
13pub(crate) mod slo;
14
15use anyhow::Result;
16use clap::{Parser, Subcommand};
17
18/// Datadog: read-only API operations.
19#[derive(Parser)]
20pub struct DatadogCommand {
21    /// The Datadog subcommand to execute.
22    #[command(subcommand)]
23    pub command: DatadogSubcommands,
24}
25
26/// Datadog subcommands.
27#[derive(Subcommand)]
28pub enum DatadogSubcommands {
29    /// Manages Datadog API credentials.
30    Auth(auth::AuthCommand),
31    /// Inspects Datadog dashboards.
32    Dashboard(dashboard::DashboardCommand),
33    /// Inspects Datadog scheduled downtimes.
34    Downtime(downtime::DowntimeCommand),
35    /// Inspects the Datadog events stream.
36    Events(events::EventsCommand),
37    /// Inspects Datadog reporting hosts.
38    Hosts(hosts::HostsCommand),
39    /// Searches Datadog logs.
40    Logs(logs::LogsCommand),
41    /// Queries Datadog metrics.
42    Metrics(metrics::MetricsCommand),
43    /// Inspects Datadog monitors.
44    Monitor(monitor::MonitorCommand),
45    /// Inspects Datadog Service Level Objectives.
46    Slo(slo::SloCommand),
47}
48
49impl DatadogCommand {
50    /// Executes the Datadog command.
51    pub async fn execute(self) -> Result<()> {
52        match self.command {
53            DatadogSubcommands::Auth(cmd) => cmd.execute().await,
54            DatadogSubcommands::Dashboard(cmd) => cmd.execute().await,
55            DatadogSubcommands::Downtime(cmd) => cmd.execute().await,
56            DatadogSubcommands::Events(cmd) => cmd.execute().await,
57            DatadogSubcommands::Hosts(cmd) => cmd.execute().await,
58            DatadogSubcommands::Logs(cmd) => cmd.execute().await,
59            DatadogSubcommands::Metrics(cmd) => cmd.execute().await,
60            DatadogSubcommands::Monitor(cmd) => cmd.execute().await,
61            DatadogSubcommands::Slo(cmd) => cmd.execute().await,
62        }
63    }
64}
65
66#[cfg(test)]
67#[allow(clippy::unwrap_used)]
68mod tests {
69    use super::*;
70    use crate::cli::datadog::format::OutputFormat;
71    use crate::datadog::test_support::{with_empty_home, EnvGuard};
72
73    #[test]
74    fn datadog_subcommands_auth_variant() {
75        let cmd = DatadogCommand {
76            command: DatadogSubcommands::Auth(auth::AuthCommand {
77                command: auth::AuthSubcommands::Status(auth::StatusCommand),
78            }),
79        };
80        assert!(matches!(cmd.command, DatadogSubcommands::Auth(_)));
81    }
82
83    #[test]
84    fn datadog_subcommands_metrics_variant() {
85        let cmd = DatadogCommand {
86            command: DatadogSubcommands::Metrics(metrics::MetricsCommand {
87                command: metrics::MetricsSubcommands::Query(metrics::query::QueryCommand {
88                    query: "m".into(),
89                    from: "1h".into(),
90                    to: None,
91                    output: OutputFormat::Table,
92                }),
93            }),
94        };
95        assert!(matches!(cmd.command, DatadogSubcommands::Metrics(_)));
96    }
97
98    #[tokio::test]
99    async fn datadog_command_dispatches_auth_logout() {
100        let guard = EnvGuard::take();
101        let _dir = with_empty_home(&guard);
102        let cmd = DatadogCommand {
103            command: DatadogSubcommands::Auth(auth::AuthCommand {
104                command: auth::AuthSubcommands::Logout(auth::LogoutCommand),
105            }),
106        };
107        cmd.execute().await.unwrap();
108    }
109
110    #[tokio::test]
111    async fn datadog_command_dispatches_metrics_query() {
112        let guard = EnvGuard::take();
113        let _dir = with_empty_home(&guard);
114        let cmd = DatadogCommand {
115            command: DatadogSubcommands::Metrics(metrics::MetricsCommand {
116                command: metrics::MetricsSubcommands::Query(metrics::query::QueryCommand {
117                    query: "m".into(),
118                    from: "1h".into(),
119                    to: None,
120                    output: OutputFormat::Table,
121                }),
122            }),
123        };
124        // Fails at credential loading, not at dispatch — which is what we're
125        // verifying here: the Metrics arm is wired through.
126        let err = cmd.execute().await.unwrap_err();
127        assert!(err.to_string().contains("not configured"));
128    }
129
130    #[tokio::test]
131    async fn datadog_command_dispatches_monitor_get() {
132        let guard = EnvGuard::take();
133        let _dir = with_empty_home(&guard);
134        let cmd = DatadogCommand {
135            command: DatadogSubcommands::Monitor(monitor::MonitorCommand {
136                command: monitor::MonitorSubcommands::Get(monitor::get::GetCommand {
137                    id: 1,
138                    output: OutputFormat::Table,
139                }),
140            }),
141        };
142        // Fails at credential loading, not at dispatch — verifies the Monitor
143        // arm is wired through to a leaf command's `execute`.
144        let err = cmd.execute().await.unwrap_err();
145        assert!(err.to_string().contains("not configured"));
146    }
147
148    #[tokio::test]
149    async fn datadog_command_dispatches_monitor_list() {
150        let guard = EnvGuard::take();
151        let _dir = with_empty_home(&guard);
152        let cmd = DatadogCommand {
153            command: DatadogSubcommands::Monitor(monitor::MonitorCommand {
154                command: monitor::MonitorSubcommands::List(monitor::list::ListCommand {
155                    name: None,
156                    tags: None,
157                    monitor_tags: None,
158                    limit: 5,
159                    output: OutputFormat::Table,
160                }),
161            }),
162        };
163        let err = cmd.execute().await.unwrap_err();
164        assert!(err.to_string().contains("not configured"));
165    }
166
167    #[tokio::test]
168    async fn datadog_command_dispatches_monitor_search() {
169        let guard = EnvGuard::take();
170        let _dir = with_empty_home(&guard);
171        let cmd = DatadogCommand {
172            command: DatadogSubcommands::Monitor(monitor::MonitorCommand {
173                command: monitor::MonitorSubcommands::Search(monitor::search::SearchCommand {
174                    query: "q".into(),
175                    limit: 5,
176                    output: OutputFormat::Table,
177                }),
178            }),
179        };
180        let err = cmd.execute().await.unwrap_err();
181        assert!(err.to_string().contains("not configured"));
182    }
183
184    #[test]
185    fn datadog_subcommands_dashboard_variant() {
186        let cmd = DatadogCommand {
187            command: DatadogSubcommands::Dashboard(dashboard::DashboardCommand {
188                command: dashboard::DashboardSubcommands::List(dashboard::list::ListCommand {
189                    filter_shared: false,
190                    output: OutputFormat::Table,
191                }),
192            }),
193        };
194        assert!(matches!(cmd.command, DatadogSubcommands::Dashboard(_)));
195    }
196
197    #[tokio::test]
198    async fn datadog_command_dispatches_dashboard_list() {
199        let guard = EnvGuard::take();
200        let _dir = with_empty_home(&guard);
201        let cmd = DatadogCommand {
202            command: DatadogSubcommands::Dashboard(dashboard::DashboardCommand {
203                command: dashboard::DashboardSubcommands::List(dashboard::list::ListCommand {
204                    filter_shared: true,
205                    output: OutputFormat::Table,
206                }),
207            }),
208        };
209        let err = cmd.execute().await.unwrap_err();
210        assert!(err.to_string().contains("not configured"));
211    }
212
213    #[tokio::test]
214    async fn datadog_command_dispatches_dashboard_get() {
215        let guard = EnvGuard::take();
216        let _dir = with_empty_home(&guard);
217        let cmd = DatadogCommand {
218            command: DatadogSubcommands::Dashboard(dashboard::DashboardCommand {
219                command: dashboard::DashboardSubcommands::Get(dashboard::get::GetCommand {
220                    id: "abc".into(),
221                    output: OutputFormat::Table,
222                }),
223            }),
224        };
225        let err = cmd.execute().await.unwrap_err();
226        assert!(err.to_string().contains("not configured"));
227    }
228
229    #[test]
230    fn datadog_subcommands_logs_variant() {
231        let cmd = DatadogCommand {
232            command: DatadogSubcommands::Logs(logs::LogsCommand {
233                command: logs::LogsSubcommands::Search(logs::search::SearchCommand {
234                    filter: "*".into(),
235                    from: "15m".into(),
236                    to: "now".into(),
237                    limit: 10,
238                    sort: logs::search::SortArg::TimestampDesc,
239                    output: OutputFormat::Table,
240                }),
241            }),
242        };
243        assert!(matches!(cmd.command, DatadogSubcommands::Logs(_)));
244    }
245
246    #[tokio::test]
247    async fn datadog_command_dispatches_logs_search() {
248        let guard = EnvGuard::take();
249        let _dir = with_empty_home(&guard);
250        let cmd = DatadogCommand {
251            command: DatadogSubcommands::Logs(logs::LogsCommand {
252                command: logs::LogsSubcommands::Search(logs::search::SearchCommand {
253                    filter: "*".into(),
254                    from: "15m".into(),
255                    to: "now".into(),
256                    limit: 10,
257                    sort: logs::search::SortArg::TimestampDesc,
258                    output: OutputFormat::Table,
259                }),
260            }),
261        };
262        let err = cmd.execute().await.unwrap_err();
263        assert!(err.to_string().contains("not configured"));
264    }
265
266    #[test]
267    fn datadog_subcommands_events_variant() {
268        let cmd = DatadogCommand {
269            command: DatadogSubcommands::Events(events::EventsCommand {
270                command: events::EventsSubcommands::List(events::list::ListCommand {
271                    filter: None,
272                    from: "1h".into(),
273                    to: "now".into(),
274                    limit: 10,
275                    sources: None,
276                    tags: None,
277                    output: OutputFormat::Table,
278                }),
279            }),
280        };
281        assert!(matches!(cmd.command, DatadogSubcommands::Events(_)));
282    }
283
284    #[tokio::test]
285    async fn datadog_command_dispatches_events_list() {
286        let guard = EnvGuard::take();
287        let _dir = with_empty_home(&guard);
288        let cmd = DatadogCommand {
289            command: DatadogSubcommands::Events(events::EventsCommand {
290                command: events::EventsSubcommands::List(events::list::ListCommand {
291                    filter: None,
292                    from: "1h".into(),
293                    to: "now".into(),
294                    limit: 10,
295                    sources: None,
296                    tags: None,
297                    output: OutputFormat::Table,
298                }),
299            }),
300        };
301        let err = cmd.execute().await.unwrap_err();
302        assert!(err.to_string().contains("not configured"));
303    }
304
305    #[test]
306    fn datadog_subcommands_slo_variant() {
307        let cmd = DatadogCommand {
308            command: DatadogSubcommands::Slo(slo::SloCommand {
309                command: slo::SloSubcommands::List(slo::list::ListCommand {
310                    tags: None,
311                    query: None,
312                    ids: None,
313                    metrics_query: None,
314                    limit: 5,
315                    output: OutputFormat::Table,
316                }),
317            }),
318        };
319        assert!(matches!(cmd.command, DatadogSubcommands::Slo(_)));
320    }
321
322    #[tokio::test]
323    async fn datadog_command_dispatches_slo_list() {
324        let guard = EnvGuard::take();
325        let _dir = with_empty_home(&guard);
326        let cmd = DatadogCommand {
327            command: DatadogSubcommands::Slo(slo::SloCommand {
328                command: slo::SloSubcommands::List(slo::list::ListCommand {
329                    tags: None,
330                    query: None,
331                    ids: None,
332                    metrics_query: None,
333                    limit: 5,
334                    output: OutputFormat::Table,
335                }),
336            }),
337        };
338        let err = cmd.execute().await.unwrap_err();
339        assert!(err.to_string().contains("not configured"));
340    }
341
342    #[tokio::test]
343    async fn datadog_command_dispatches_slo_get() {
344        let guard = EnvGuard::take();
345        let _dir = with_empty_home(&guard);
346        let cmd = DatadogCommand {
347            command: DatadogSubcommands::Slo(slo::SloCommand {
348                command: slo::SloSubcommands::Get(slo::get::GetCommand {
349                    id: "abc".into(),
350                    output: OutputFormat::Table,
351                }),
352            }),
353        };
354        let err = cmd.execute().await.unwrap_err();
355        assert!(err.to_string().contains("not configured"));
356    }
357
358    #[test]
359    fn datadog_subcommands_hosts_variant() {
360        let cmd = DatadogCommand {
361            command: DatadogSubcommands::Hosts(hosts::HostsCommand {
362                command: hosts::HostsSubcommands::List(hosts::list::ListCommand {
363                    filter: None,
364                    from: None,
365                    limit: 5,
366                    output: OutputFormat::Table,
367                }),
368            }),
369        };
370        assert!(matches!(cmd.command, DatadogSubcommands::Hosts(_)));
371    }
372
373    #[tokio::test]
374    async fn datadog_command_dispatches_hosts_list() {
375        let guard = EnvGuard::take();
376        let _dir = with_empty_home(&guard);
377        let cmd = DatadogCommand {
378            command: DatadogSubcommands::Hosts(hosts::HostsCommand {
379                command: hosts::HostsSubcommands::List(hosts::list::ListCommand {
380                    filter: None,
381                    from: None,
382                    limit: 5,
383                    output: OutputFormat::Table,
384                }),
385            }),
386        };
387        let err = cmd.execute().await.unwrap_err();
388        assert!(err.to_string().contains("not configured"));
389    }
390
391    #[test]
392    fn datadog_subcommands_downtime_variant() {
393        let cmd = DatadogCommand {
394            command: DatadogSubcommands::Downtime(downtime::DowntimeCommand {
395                command: downtime::DowntimeSubcommands::List(downtime::list::ListCommand {
396                    active_only: false,
397                    output: OutputFormat::Table,
398                }),
399            }),
400        };
401        assert!(matches!(cmd.command, DatadogSubcommands::Downtime(_)));
402    }
403
404    #[tokio::test]
405    async fn datadog_command_dispatches_downtime_list() {
406        let guard = EnvGuard::take();
407        let _dir = with_empty_home(&guard);
408        let cmd = DatadogCommand {
409            command: DatadogSubcommands::Downtime(downtime::DowntimeCommand {
410                command: downtime::DowntimeSubcommands::List(downtime::list::ListCommand {
411                    active_only: true,
412                    output: OutputFormat::Table,
413                }),
414            }),
415        };
416        let err = cmd.execute().await.unwrap_err();
417        assert!(err.to_string().contains("not configured"));
418    }
419
420    #[tokio::test]
421    async fn datadog_command_dispatches_metrics_catalog_list() {
422        let guard = EnvGuard::take();
423        let _dir = with_empty_home(&guard);
424        let cmd = DatadogCommand {
425            command: DatadogSubcommands::Metrics(metrics::MetricsCommand {
426                command: metrics::MetricsSubcommands::Catalog(metrics::catalog::CatalogCommand {
427                    command: metrics::catalog::CatalogSubcommands::List(
428                        metrics::catalog::list::ListCommand {
429                            host: None,
430                            from: None,
431                            output: OutputFormat::Table,
432                        },
433                    ),
434                }),
435            }),
436        };
437        let err = cmd.execute().await.unwrap_err();
438        assert!(err.to_string().contains("not configured"));
439    }
440}