Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ pup infrastructure hosts list

### Cost & Usage
- **usage** - Usage and billing (summary, hourly)
- **costs** - Cost management: `datadog` subgroup (projected, attribution, by-org, aws-config, azure-config, gcp-config) and `ccm` subgroup (custom-costs, tag-descriptions, tag-metadata, tags, tag-keys, budgets, commitments)
- **costs** - Cost management: `datadog` subgroup (projected, attribution, by-org, aws-config, azure-config, gcp-config), `ccm` subgroup (custom-costs, tag-descriptions, tag-metadata, tags, tag-keys, budgets, commitments), `oci-configs` subgroup (list), and `anomalies` subgroup (list)

### Configuration & Data Management
- **obs-pipelines** - Observability pipelines (list, get, create, update, delete, validate)
Expand Down
16 changes: 13 additions & 3 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,8 @@ static UNSTABLE_OPS: &[&str] = &[
"v2.get_investigation",
"v2.list_investigations",
"v2.trigger_investigation",
// Cloud Cost Management — Anomalies (1)
"v2.list_cost_anomalies",
];

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -636,7 +638,7 @@ static OAUTH_EXCLUDED_ENDPOINTS: &[EndpointRequirement] = &[
path: "/api/v2/obs-pipelines/pipelines/validate",
method: "POST",
},
// Cost / Billing (9) — API key only, no OAuth support
// Cost / Billing (11) — API key only, no OAuth support
EndpointRequirement {
path: "/api/v2/usage/projected_cost",
method: "GET",
Expand Down Expand Up @@ -698,6 +700,14 @@ static OAUTH_EXCLUDED_ENDPOINTS: &[EndpointRequirement] = &[
path: "/api/v2/cost/gcp_uc_config/",
method: "DELETE",
},
EndpointRequirement {
path: "/api/v2/cost/oci_config",
method: "GET",
},
EndpointRequirement {
path: "/api/v2/cost/anomalies",
method: "GET",
},
// Profiling (4)
// No OAuth scope is declared for Continuous Profiler endpoints; force API-key auth.
EndpointRequirement {
Expand Down Expand Up @@ -1273,12 +1283,12 @@ mod tests {

#[test]
fn test_unstable_ops_count() {
assert_eq!(UNSTABLE_OPS.len(), 162);
assert_eq!(UNSTABLE_OPS.len(), 163);
}

#[test]
fn test_oauth_excluded_count() {
assert_eq!(OAUTH_EXCLUDED_ENDPOINTS.len(), 52);
assert_eq!(OAUTH_EXCLUDED_ENDPOINTS.len(), 54);
}

#[test]
Expand Down
69 changes: 68 additions & 1 deletion src/commands/cost.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use anyhow::Result;
use datadog_api_client::datadogV2::api_cloud_cost_management::CloudCostManagementAPI;
use datadog_api_client::datadogV2::api_cloud_cost_management::{
CloudCostManagementAPI, ListCostAnomaliesOptionalParams,
};
use datadog_api_client::datadogV2::api_usage_metering::{
GetCostByOrgOptionalParams, GetMonthlyCostAttributionOptionalParams,
GetProjectedCostOptionalParams, UsageMeteringAPI as UsageMeteringV2API,
Expand Down Expand Up @@ -179,6 +181,28 @@ pub async fn gcp_config_delete(cfg: &Config, id: i64) -> Result<()> {
Ok(())
}

// ---- Cloud Cost Management — OCI Configs ----

pub async fn oci_configs_list(cfg: &Config) -> Result<()> {
let api = make_ccm_api(cfg);
let resp = api
.list_cost_oci_configs()
.await
.map_err(|e| anyhow::anyhow!("failed to list OCI configs: {e:?}"))?;
formatter::output(cfg, &resp)
}

// ---- Cloud Cost Management — Anomalies ----

pub async fn anomalies_list(cfg: &Config) -> Result<()> {
let api = make_ccm_api(cfg);
let resp = api
.list_cost_anomalies(ListCostAnomaliesOptionalParams::default())
.await
.map_err(|e| anyhow::anyhow!("failed to list cost anomalies: {e:?}"))?;
formatter::output(cfg, &resp)
}

#[cfg(test)]
mod tests {

Expand All @@ -193,4 +217,47 @@ mod tests {
let _ = super::projected(&cfg).await;
cleanup_env();
}

#[tokio::test]
async fn test_oci_configs_list() {
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":[]}"#).await;
let result = super::oci_configs_list(&cfg).await;
assert!(
result.is_ok(),
"oci_configs_list failed: {:?}",
result.err()
);
cleanup_env();
}

#[tokio::test]
async fn test_oci_configs_list_error() {
let _lock = lock_env().await;
let mut server = mockito::Server::new_async().await;
let cfg = test_config(&server.url());
let _mock = server
.mock("GET", mockito::Matcher::Any)
.with_status(403)
.with_header("content-type", "application/json")
.with_body(r#"{"errors":["Forbidden"]}"#)
.create_async()
.await;
let result = super::oci_configs_list(&cfg).await;
assert!(result.is_err(), "expected error for 403 response");
cleanup_env();
}

#[tokio::test]
async fn test_anomalies_list() {
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":null}"#).await;
let result = super::anomalies_list(&cfg).await;
assert!(result.is_ok(), "anomalies_list failed: {:?}", result.err());
cleanup_env();
}
}
31 changes: 31 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7492,6 +7492,17 @@ enum CostActions {
#[command(subcommand)]
action: CostCcmActions,
},
/// Manage OCI (Oracle Cloud Infrastructure) cost configs
#[command(name = "oci-configs")]
OciConfigs {
#[command(subcommand)]
action: CostOciConfigsActions,
},
/// Manage Cloud Cost Management anomalies
Anomalies {
#[command(subcommand)]
action: CostAnomaliesActions,
},
}

#[derive(Subcommand)]
Expand Down Expand Up @@ -7919,6 +7930,20 @@ enum CostCcmCommitmentsActions {
},
}

// ---- Cost OCI Configs ----
#[derive(Subcommand)]
enum CostOciConfigsActions {
/// List OCI cost configs
List,
}

// ---- Cost Anomalies ----
#[derive(Subcommand)]
enum CostAnomaliesActions {
/// List detected cost anomalies
List,
}

// ---- Misc ----
#[derive(Subcommand)]
enum MiscActions {
Expand Down Expand Up @@ -13903,6 +13928,12 @@ async fn main_inner() -> anyhow::Result<()> {
}
},
},
CostActions::OciConfigs { action } => match action {
CostOciConfigsActions::List => commands::cost::oci_configs_list(&cfg).await?,
},
CostActions::Anomalies { action } => match action {
CostAnomaliesActions::List => commands::cost::anomalies_list(&cfg).await?,
},
}
}
// --- Misc ---
Expand Down
Loading