diff --git a/docs/EXAMPLES.md b/docs/EXAMPLES.md index 3fd5e817..c3c80cce 100644 --- a/docs/EXAMPLES.md +++ b/docs/EXAMPLES.md @@ -754,8 +754,19 @@ pup users get "user-id" ### List Roles ```bash pup users roles list + +# Paginate results (max page size 100) +pup users roles list --page-size=50 --page-number=1 + +# Sort by name descending and filter by a search string +pup users roles list --sort="-name" --filter="admin" + +# Filter by specific role IDs +pup users roles list --filter-id="id1,id2" ``` +Sort accepts: `name`, `-name`, `modified_at`, `-modified_at`, `user_count`, `-user_count` (a leading `-` sorts descending). + ### Get Organization ```bash pup organizations get diff --git a/src/commands/users.rs b/src/commands/users.rs index 75fe2a19..dba8d877 100644 --- a/src/commands/users.rs +++ b/src/commands/users.rs @@ -4,6 +4,7 @@ use datadog_api_client::datadogV2::api_service_accounts::{ ListServiceAccountApplicationKeysOptionalParams, ServiceAccountsAPI, }; use datadog_api_client::datadogV2::api_users::{ListUsersOptionalParams, UsersAPI}; +use datadog_api_client::datadogV2::model::RolesSort; use crate::config::Config; use crate::formatter; @@ -31,10 +32,47 @@ pub async fn get(cfg: &Config, id: &str) -> Result<()> { formatter::output(cfg, &resp) } -pub async fn roles_list(cfg: &Config) -> Result<()> { +fn parse_roles_sort(s: &str) -> Result { + Ok(match s { + "name" => RolesSort::NAME_ASCENDING, + "-name" => RolesSort::NAME_DESCENDING, + "modified_at" => RolesSort::MODIFIED_AT_ASCENDING, + "-modified_at" => RolesSort::MODIFIED_AT_DESCENDING, + "user_count" => RolesSort::USER_COUNT_ASCENDING, + "-user_count" => RolesSort::USER_COUNT_DESCENDING, + other => anyhow::bail!( + "invalid sort '{other}': expected one of name, -name, modified_at, -modified_at, user_count, -user_count" + ), + }) +} + +pub async fn roles_list( + cfg: &Config, + page_size: Option, + page_number: Option, + sort: Option, + filter: Option, + filter_id: Option, +) -> Result<()> { let api = crate::make_api!(RolesAPI, cfg); + let mut params = ListRolesOptionalParams::default(); + if let Some(n) = page_size { + params.page_size = Some(n); + } + if let Some(n) = page_number { + params.page_number = Some(n); + } + if let Some(s) = sort { + params.sort = Some(parse_roles_sort(&s)?); + } + if let Some(f) = filter { + params.filter = Some(f); + } + if let Some(f) = filter_id { + params.filter_id = Some(f); + } let resp = api - .list_roles(ListRolesOptionalParams::default()) + .list_roles(params) .await .map_err(|e| anyhow::anyhow!("failed to list roles: {e:?}"))?; formatter::output(cfg, &resp) @@ -154,10 +192,54 @@ mod tests { let mut s = mockito::Server::new_async().await; let cfg = test_config(&s.url()); mock_all(&mut s, r#"{"data": []}"#).await; - let _ = super::roles_list(&cfg).await; + let _ = super::roles_list(&cfg, None, None, None, None, None).await; + cleanup_env(); + } + + #[tokio::test] + async fn test_users_roles_list_with_pagination() { + let _lock = lock_env().await; + let mut s = mockito::Server::new_async().await; + let cfg = test_config(&s.url()); + mock_all(&mut s, r#"{"data": []}"#).await; + let result = super::roles_list( + &cfg, + Some(50), + Some(1), + Some("-name".to_string()), + Some("admin".to_string()), + Some("id1,id2".to_string()), + ) + .await; + assert!(result.is_ok(), "roles list failed: {:?}", result.err()); cleanup_env(); } + #[tokio::test] + async fn test_users_roles_list_invalid_sort() { + let _lock = lock_env().await; + let mut s = mockito::Server::new_async().await; + let cfg = test_config(&s.url()); + mock_all(&mut s, r#"{"data": []}"#).await; + let result = + super::roles_list(&cfg, None, None, Some("bogus".to_string()), None, None).await; + assert!(result.is_err(), "expected error for invalid sort value"); + cleanup_env(); + } + + #[test] + fn test_parse_roles_sort() { + assert!(matches!( + super::parse_roles_sort("name").unwrap(), + super::RolesSort::NAME_ASCENDING + )); + assert!(matches!( + super::parse_roles_sort("-user_count").unwrap(), + super::RolesSort::USER_COUNT_DESCENDING + )); + assert!(super::parse_roles_sort("nope").is_err()); + } + #[tokio::test] async fn test_service_account_app_keys_list() { let _lock = lock_env().await; diff --git a/src/main.rs b/src/main.rs index 4e8f552f..16095f7f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4703,7 +4703,27 @@ enum UserActions { #[derive(Subcommand)] enum UserRoleActions { /// List roles - List, + List { + #[arg( + long = "page-size", + help = "Number of items to return per page (max 100)" + )] + page_size: Option, + #[arg(long = "page-number", help = "Specific page number to return")] + page_number: Option, + #[arg( + long, + help = "Sort field: name, -name, modified_at, -modified_at, user_count, -user_count" + )] + sort: Option, + #[arg(long, help = "Filter all roles by the given string")] + filter: Option, + #[arg( + long = "filter-id", + help = "Filter all roles by the given list of role IDs" + )] + filter_id: Option, + }, } #[derive(Subcommand)] @@ -13822,7 +13842,23 @@ async fn main_inner() -> anyhow::Result<()> { } => commands::users::list(&cfg, page_size, page_number).await?, UserActions::Get { user_id } => commands::users::get(&cfg, &user_id).await?, UserActions::Roles { action } => match action { - UserRoleActions::List => commands::users::roles_list(&cfg).await?, + UserRoleActions::List { + page_size, + page_number, + sort, + filter, + filter_id, + } => { + commands::users::roles_list( + &cfg, + page_size, + page_number, + sort, + filter, + filter_id, + ) + .await? + } }, UserActions::Seats { action } => match action { SeatsActions::Users { action } => match action {