Repository navigation
Add Agent Health issues list and get endpoints #996
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,221 @@ | ||||||||
| // Code generated by openapi-transformer. DO NOT EDIT. | ||||||||
|
|
||||||||
| use anyhow::Result; | ||||||||
| use datadog_api_client::datadogV2::api_agent_health::{ | ||||||||
| AgentHealthAPI, ListAgentHealthIssuesOptionalParams, | ||||||||
| }; | ||||||||
|
|
||||||||
| use crate::config::Config; | ||||||||
| use crate::formatter; | ||||||||
|
|
||||||||
| #[derive(clap::Subcommand)] | ||||||||
| pub enum Command { | ||||||||
| /// Get an Agent Health issue | ||||||||
| /// | ||||||||
| /// Get an Agent Health issue and its available remediation in your organization. | ||||||||
| /// Only open issues are returned. The `remediation` attribute is omitted when | ||||||||
| /// the issue has no remediation. | ||||||||
| /// | ||||||||
| /// Returns `404` if the issue does not exist, is not open, belongs to another | ||||||||
| /// organization, or you do not have access to its data. This response does not | ||||||||
| /// reveal whether the issue exists. Returns `403` if you lack the `hosts_read` permission. | ||||||||
| Get { | ||||||||
| /// The unique identifier of the issue. It may contain `:` and must be percent-encoded | ||||||||
| /// by clients when included in the path. | ||||||||
| issue_id: String, | ||||||||
| }, | ||||||||
| /// List Agent Health issues | ||||||||
| /// | ||||||||
| /// List Agent Health issues across your organization. Only open issues are returned. | ||||||||
| /// Returns `403` if you lack the `hosts_read` permission. | ||||||||
| /// | ||||||||
| /// Results use a stable order: severity rank (`high`, `medium`, `low`, then other values), | ||||||||
| /// then `first_seen_at` descending with missing timestamps last, then issue ID ascending. | ||||||||
| /// Use `page[size]` and the zero-based `page[number]` to paginate. Invalid page values | ||||||||
| /// return a `400` JSON:API error response. `meta.page.total_filtered_count` is the number | ||||||||
| /// of issues matching the filters, and `meta.page.total_count` the number of open issues | ||||||||
| /// in the organization. | ||||||||
| /// | ||||||||
| /// Each filter accepts up to 50 comma-separated exact values, combined with OR within | ||||||||
| /// a filter and AND across filters. Blank entries are ignored; an all-blank filter | ||||||||
| /// is treated as absent. | ||||||||
| List { | ||||||||
| /// The number of issues to return per page, from 1 to 100. Defaults to 25. | ||||||||
| #[arg(long = "page-size")] | ||||||||
| page_size: Option<i64>, | ||||||||
| /// The zero-based page number, from 0 to 1000000. Defaults to 0. | ||||||||
| #[arg(long = "page-number")] | ||||||||
| page_number: Option<i64>, | ||||||||
| /// Filter by a comma-separated list of exact `severity` values, up to 50 values. | ||||||||
| /// Values are combined with OR within this filter and AND across filters. | ||||||||
| /// Blank entries are ignored; an all-blank filter is treated as absent. | ||||||||
| #[arg(long = "filter-severity")] | ||||||||
| filter_severity: Option<String>, | ||||||||
| /// Filter by a comma-separated list of exact `category` values, up to 50 values. | ||||||||
| /// Values are combined with OR within this filter and AND across filters. | ||||||||
| /// Blank entries are ignored; an all-blank filter is treated as absent. | ||||||||
| #[arg(long = "filter-category")] | ||||||||
| filter_category: Option<String>, | ||||||||
| /// Filter by a comma-separated list of exact `issue_type` values, up to 50 values. | ||||||||
| /// Values are combined with OR within this filter and AND across filters. | ||||||||
| /// Blank entries are ignored; an all-blank filter is treated as absent. | ||||||||
| #[arg(long = "filter-issue-type")] | ||||||||
| filter_issue_type: Option<String>, | ||||||||
| /// Filter by a comma-separated list of exact `resource_kind` values, up to 50 values. | ||||||||
| /// Values are combined with OR within this filter and AND across filters. | ||||||||
| /// Blank entries are ignored; an all-blank filter is treated as absent. | ||||||||
| #[arg(long = "filter-resource-kind")] | ||||||||
| filter_resource_kind: Option<String>, | ||||||||
| /// Filter by a comma-separated list of exact `resource_id` values, up to 50 values. | ||||||||
| /// Values are combined with OR within this filter and AND across filters. | ||||||||
| /// Blank entries are ignored; an all-blank filter is treated as absent. | ||||||||
| #[arg(long = "filter-resource-id")] | ||||||||
| filter_resource_id: Option<String>, | ||||||||
| }, | ||||||||
| } | ||||||||
|
|
||||||||
| pub async fn run(cfg: &Config, command: Command) -> Result<()> { | ||||||||
| match command { | ||||||||
| Command::Get { issue_id } => get(cfg, issue_id).await, | ||||||||
| Command::List { | ||||||||
| page_size, | ||||||||
| page_number, | ||||||||
| filter_severity, | ||||||||
| filter_category, | ||||||||
| filter_issue_type, | ||||||||
| filter_resource_kind, | ||||||||
| filter_resource_id, | ||||||||
| } => { | ||||||||
| list( | ||||||||
| cfg, | ||||||||
| page_size, | ||||||||
| page_number, | ||||||||
| filter_severity, | ||||||||
| filter_category, | ||||||||
| filter_issue_type, | ||||||||
| filter_resource_kind, | ||||||||
| filter_resource_id, | ||||||||
| ) | ||||||||
| .await | ||||||||
| } | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| /// Get an Agent Health issue | ||||||||
| /// | ||||||||
| /// Get an Agent Health issue and its available remediation in your organization. | ||||||||
| /// Only open issues are returned. The `remediation` attribute is omitted when | ||||||||
| /// the issue has no remediation. | ||||||||
| /// | ||||||||
| /// Returns `404` if the issue does not exist, is not open, belongs to another | ||||||||
| /// organization, or you do not have access to its data. This response does not | ||||||||
| /// reveal whether the issue exists. Returns `403` if you lack the `hosts_read` permission. | ||||||||
| pub async fn get(cfg: &Config, issue_id: String) -> Result<()> { | ||||||||
| let api = crate::make_api!(AgentHealthAPI, cfg); | ||||||||
| let resp = api | ||||||||
| .get_agent_health_issue(issue_id) | ||||||||
| .await | ||||||||
| .map_err(|e| anyhow::anyhow!("failed to get_agent_health_issue: {:?}", e))?; | ||||||||
| formatter::output(cfg, &resp) | ||||||||
| } | ||||||||
|
|
||||||||
| /// List Agent Health issues | ||||||||
| /// | ||||||||
| /// List Agent Health issues across your organization. Only open issues are returned. | ||||||||
| /// Returns `403` if you lack the `hosts_read` permission. | ||||||||
| /// | ||||||||
| /// Results use a stable order: severity rank (`high`, `medium`, `low`, then other values), | ||||||||
| /// then `first_seen_at` descending with missing timestamps last, then issue ID ascending. | ||||||||
| /// Use `page[size]` and the zero-based `page[number]` to paginate. Invalid page values | ||||||||
| /// return a `400` JSON:API error response. `meta.page.total_filtered_count` is the number | ||||||||
| /// of issues matching the filters, and `meta.page.total_count` the number of open issues | ||||||||
| /// in the organization. | ||||||||
| /// | ||||||||
| /// Each filter accepts up to 50 comma-separated exact values, combined with OR within | ||||||||
| /// a filter and AND across filters. Blank entries are ignored; an all-blank filter | ||||||||
| /// is treated as absent. | ||||||||
| pub async fn list( | ||||||||
| cfg: &Config, | ||||||||
| page_size: Option<i64>, | ||||||||
| page_number: Option<i64>, | ||||||||
| filter_severity: Option<String>, | ||||||||
| filter_category: Option<String>, | ||||||||
| filter_issue_type: Option<String>, | ||||||||
| filter_resource_kind: Option<String>, | ||||||||
| filter_resource_id: Option<String>, | ||||||||
| ) -> Result<()> { | ||||||||
| let api = crate::make_api!(AgentHealthAPI, cfg); | ||||||||
| let mut params = ListAgentHealthIssuesOptionalParams::default(); | ||||||||
| if let Some(v) = page_size { | ||||||||
| params = params.page_size(v); | ||||||||
| } | ||||||||
| if let Some(v) = page_number { | ||||||||
| params = params.page_number(v); | ||||||||
| } | ||||||||
| if let Some(v) = filter_severity { | ||||||||
| params = params.filter_severity(v); | ||||||||
| } | ||||||||
| if let Some(v) = filter_category { | ||||||||
| params = params.filter_category(v); | ||||||||
| } | ||||||||
| if let Some(v) = filter_issue_type { | ||||||||
| params = params.filter_issue_type(v); | ||||||||
| } | ||||||||
| if let Some(v) = filter_resource_kind { | ||||||||
| params = params.filter_resource_kind(v); | ||||||||
| } | ||||||||
| if let Some(v) = filter_resource_id { | ||||||||
| params = params.filter_resource_id(v); | ||||||||
| } | ||||||||
| let resp = api | ||||||||
| .list_agent_health_issues(params) | ||||||||
| .await | ||||||||
| .map_err(|e| anyhow::anyhow!("failed to list_agent_health_issues: {:?}", e))?; | ||||||||
| let count = resp.data.len(); | ||||||||
| let truncated = page_size.is_some_and(|l| count >= l as usize); | ||||||||
| let next_action = if truncated { | ||||||||
| page_size.map(|l| { | ||||||||
| format!( | ||||||||
| "Results may be truncated at {l}. Use --page-size={} or narrow the filters", | ||||||||
| l + 1 | ||||||||
|
Comment on lines
+179
to
+180
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When agent-health list --page-size=100 returns a full page, the continuation hint recommends --page-size=101, exceeding the endpoint's maximum and causing HTTP 400. Increasing page size also shifts page boundaries when --page-number is supplied. Direct users to the next page while preserving page size and filters.
Suggested change
React: 👍 useful · 👎 wrong · 😕 unclear · 👀 right, but not acting on it What each reaction means
🤖 Bits Code Review · Open Bits AI session |
||||||||
| ) | ||||||||
| }) | ||||||||
| } else { | ||||||||
| None | ||||||||
| }; | ||||||||
| let meta = formatter::Metadata { | ||||||||
| count: Some(count), | ||||||||
| truncated, | ||||||||
| command: Some("agent-health list".to_string()), | ||||||||
| next_action, | ||||||||
| ..Default::default() | ||||||||
| }; | ||||||||
| formatter::output_with_meta(cfg, &resp, &meta) | ||||||||
| } | ||||||||
|
|
||||||||
| #[cfg(test)] | ||||||||
| mod tests { | ||||||||
| use crate::test_support::*; | ||||||||
|
|
||||||||
| #[tokio::test] | ||||||||
| async fn test_get_ok() { | ||||||||
| let _lock = lock_env().await; | ||||||||
| let mut server = mockito::Server::new_async().await; | ||||||||
| let cfg = test_config(&server.url()); | ||||||||
| let _mock = mock_any(&mut server, "GET", r##"{"data": {"attributes": {"category": "configuration", "description": "The Agent configuration file contains an invalid value.", "first_seen_at": "2026-11-01T10:00:00Z", "issue_name": "Invalid Config", "issue_type": "invalid_config", "remediation": {"script": {"content": "...", "filename": "fix.sh", "language": "bash", "language_version": "5", "requires_root": true}, "steps": [{"order": 1, "text": "Open datadog.yaml"}], "summary": "Fix the invalid configuration value."}, "resource_id": "my-hostname", "resource_kind": "host", "resource_type": "host", "severity": "high", "state": "open", "tags": ["env:prod"], "title": "Invalid Agent configuration", "updated_at": "2026-11-09T08:00:00Z"}, "id": "invalid-config:4828e7d6c704c4a3", "type": "agent_health_issues"}}"##).await; | ||||||||
| let result = super::get(&cfg, "test".to_string()).await; | ||||||||
| assert!(result.is_ok(), "get failed: {:?}", result.err()); | ||||||||
| cleanup_env(); | ||||||||
| } | ||||||||
|
|
||||||||
| #[tokio::test] | ||||||||
| async fn test_list_ok() { | ||||||||
| let _lock = lock_env().await; | ||||||||
| let mut server = mockito::Server::new_async().await; | ||||||||
| let cfg = test_config(&server.url()); | ||||||||
| let _mock = mock_any(&mut server, "GET", r##"{"data": [{"attributes": {"category": "configuration", "description": "The Agent configuration file contains an invalid value.", "first_seen_at": "2026-11-01T10:00:00Z", "issue_name": "Invalid Config", "issue_type": "invalid_config", "resource_id": "my-hostname", "resource_kind": "host", "resource_type": "host", "severity": "high", "state": "open", "tags": ["env:prod"], "title": "Invalid Agent configuration", "updated_at": "2026-11-09T08:00:00Z"}, "id": "invalid-config:4828e7d6c704c4a3", "type": "agent_health_issues"}], "meta": {"page": {"total_count": 3, "total_filtered_count": 1}}}"##).await; | ||||||||
| let result = super::list(&cfg, None, None, None, None, None, None, None).await; | ||||||||
| assert!(result.is_ok(), "list failed: {:?}", result.err()); | ||||||||
| cleanup_env(); | ||||||||
| } | ||||||||
| } | ||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,6 @@ | ||
| agent-health | ||
| agent-health.get | ||
| agent-health.list | ||
| authn-mappings | ||
| authn-mappings.create | ||
| authn-mappings.delete | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing these registrations disables all existing Databricks, Elastic Cloud, Snowflake, and Twilio account commands. Their handlers use make_api!, while the newly pinned SDK still gates these operations and returns UnstableOperationDisabledError before sending a request. Restore all 20 registrations and update the operation-count assertion to 207.
React: 👍 useful · 👎 wrong · 😕 unclear · 👀 right, but not acting on it
What each reaction means
🤖 Bits Code Review · Open Bits AI session