feat: add --dry-run support to events helper commands (#457)
Add dry-run mode to gws events +renew and gws events +subscribe commands. When --dry-run is specified, the commands print what actions would be taken without making any API calls. This allows agents to simulate requests and learn without reaching the server.
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
---
|
||||
"@googleworkspace/cli": patch
|
||||
---
|
||||
|
||||
feat(helpers): add --dry-run support to events helper commands
|
||||
|
||||
Add dry-run mode to `gws events +renew` and `gws events +subscribe` commands.
|
||||
When --dry-run is specified, the commands will print what actions would be
|
||||
taken without making any API calls. This allows agents to simulate requests
|
||||
and learn without reaching the server.
|
||||
+30
-12
@@ -30,13 +30,40 @@ pub(super) async fn handle_renew(
|
||||
matches: &ArgMatches,
|
||||
) -> Result<(), GwsError> {
|
||||
let config = parse_renew_args(matches)?;
|
||||
let dry_run = matches.get_flag("dry-run");
|
||||
|
||||
if dry_run {
|
||||
eprintln!("🏃 DRY RUN — no changes will be made\n");
|
||||
|
||||
// Handle dry-run case and exit early
|
||||
let result = if let Some(name) = config.name {
|
||||
let name = crate::validate::validate_resource_name(&name)?;
|
||||
eprintln!("Reactivating subscription: {name}");
|
||||
json!({
|
||||
"dry_run": true,
|
||||
"action": "Would reactivate subscription",
|
||||
"name": name,
|
||||
"note": "Run without --dry-run to actually reactivate the subscription"
|
||||
})
|
||||
} else {
|
||||
json!({
|
||||
"dry_run": true,
|
||||
"action": "Would list and renew subscriptions expiring within",
|
||||
"within": config.within,
|
||||
"note": "Run without --dry-run to actually renew subscriptions"
|
||||
})
|
||||
};
|
||||
println!("{}", serde_json::to_string_pretty(&result).context("Failed to serialize dry-run output")?);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Real run logic
|
||||
let client = crate::client::build_client()?;
|
||||
let ws_token = auth::get_token(&[WORKSPACE_EVENTS_SCOPE])
|
||||
.await
|
||||
.map_err(|e| GwsError::Auth(format!("Failed to get token: {e}")))?;
|
||||
|
||||
if let Some(name) = config.name {
|
||||
// Reactivate a specific subscription
|
||||
let name = crate::validate::validate_resource_name(&name)?;
|
||||
eprintln!("Reactivating subscription: {name}");
|
||||
let resp = client
|
||||
@@ -51,15 +78,9 @@ pub(super) async fn handle_renew(
|
||||
.context("Failed to reactivate subscription")?;
|
||||
|
||||
let body: Value = resp.json().await.context("Failed to parse response")?;
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&body).unwrap_or_default()
|
||||
);
|
||||
println!("{}", serde_json::to_string_pretty(&body).context("Failed to serialize response body")?);
|
||||
} else {
|
||||
let within_secs = parse_duration(&config.within)?;
|
||||
|
||||
// List all subscriptions
|
||||
let resp = client
|
||||
.get("https://workspaceevents.googleapis.com/v1/subscriptions")
|
||||
.bearer_auth(&ws_token)
|
||||
@@ -98,10 +119,7 @@ pub(super) async fn handle_renew(
|
||||
"status": "success",
|
||||
"renewed": renewed,
|
||||
});
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&result).unwrap_or_default()
|
||||
);
|
||||
println!("{}", serde_json::to_string_pretty(&result).context("Failed to serialize result")?);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -107,26 +107,53 @@ pub(super) async fn handle_subscribe(
|
||||
matches: &ArgMatches,
|
||||
) -> Result<(), GwsError> {
|
||||
let config = parse_subscribe_args(matches)?;
|
||||
let dry_run = matches.get_flag("dry-run");
|
||||
|
||||
if dry_run {
|
||||
eprintln!("🏃 DRY RUN — no changes will be made\n");
|
||||
}
|
||||
|
||||
if let Some(ref dir) = config.output_dir {
|
||||
std::fs::create_dir_all(dir).context("Failed to create output dir")?;
|
||||
if !dry_run {
|
||||
std::fs::create_dir_all(dir).context("Failed to create output dir")?;
|
||||
}
|
||||
}
|
||||
|
||||
let client = crate::client::build_client()?;
|
||||
let pubsub_token_provider = auth::token_provider(&[PUBSUB_SCOPE]);
|
||||
|
||||
// Get Pub/Sub token
|
||||
let pubsub_token = auth::get_token(&[PUBSUB_SCOPE])
|
||||
.await
|
||||
.map_err(|e| GwsError::Auth(format!("Failed to get Pub/Sub token: {e}")))?;
|
||||
|
||||
let (pubsub_subscription, topic_name, ws_subscription_name, created_resources) =
|
||||
if let Some(ref sub_name) = config.subscription {
|
||||
// Use existing subscription — no setup needed
|
||||
// (don't fetch Pub/Sub token since we won't need it for existing subscriptions)
|
||||
if dry_run {
|
||||
eprintln!("Would listen to existing subscription: {}", sub_name.0);
|
||||
let result = json!({
|
||||
"dry_run": true,
|
||||
"action": "Would listen to existing subscription",
|
||||
"subscription": sub_name.0,
|
||||
"note": "Run without --dry-run to actually start listening"
|
||||
});
|
||||
println!("{}", serde_json::to_string_pretty(&result).context("Failed to serialize dry-run output")?);
|
||||
return Ok(());
|
||||
}
|
||||
(sub_name.0.clone(), None, None, false)
|
||||
} else {
|
||||
// Get Pub/Sub token only when creating new subscription
|
||||
let pubsub_token = if dry_run {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
auth::get_token(&[PUBSUB_SCOPE])
|
||||
.await
|
||||
.map_err(|e| GwsError::Auth(format!("Failed to get Pub/Sub token: {e}")))?,
|
||||
)
|
||||
};
|
||||
|
||||
// Full setup: create Pub/Sub topic + subscription + Workspace Events subscription
|
||||
let target = config.target.clone().unwrap();
|
||||
// Validate target before use in both dry-run and actual execution paths
|
||||
let target = crate::validate::validate_resource_name(&config.target.clone().unwrap())?
|
||||
.to_string();
|
||||
let project =
|
||||
crate::validate::validate_resource_name(&config.project.clone().unwrap().0)?
|
||||
.to_string();
|
||||
@@ -140,11 +167,34 @@ pub(super) async fn handle_subscribe(
|
||||
let topic = format!("projects/{project}/topics/gws-{slug}-{suffix}");
|
||||
let sub = format!("projects/{project}/subscriptions/gws-{slug}-{suffix}");
|
||||
|
||||
// Dry-run: print what would be created and exit
|
||||
if dry_run {
|
||||
eprintln!("Would create Pub/Sub topic: {topic}");
|
||||
eprintln!("Would create Pub/Sub subscription: {sub}");
|
||||
eprintln!("Would create Workspace Events subscription for target: {target}");
|
||||
eprintln!("Would listen for event types: {}", config.event_types.join(", "));
|
||||
|
||||
let result = json!({
|
||||
"dry_run": true,
|
||||
"action": "Would create Workspace Events subscription",
|
||||
"pubsub_topic": topic,
|
||||
"pubsub_subscription": sub,
|
||||
"target": target,
|
||||
"event_types": config.event_types,
|
||||
"note": "Run without --dry-run to actually create subscription"
|
||||
});
|
||||
println!("{}", serde_json::to_string_pretty(&result).context("Failed to serialize dry-run output")?);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 1. Create Pub/Sub topic
|
||||
eprintln!("Creating Pub/Sub topic: {topic}");
|
||||
let token = pubsub_token
|
||||
.as_ref()
|
||||
.ok_or_else(|| GwsError::Auth("Token unavailable in non-dry-run mode. This indicates a bug.".to_string()))?;
|
||||
let resp = client
|
||||
.put(format!("{PUBSUB_API_BASE}/{topic}"))
|
||||
.bearer_auth(&pubsub_token)
|
||||
.bearer_auth(token)
|
||||
.header("Content-Type", "application/json")
|
||||
.body("{}")
|
||||
.send()
|
||||
@@ -169,7 +219,7 @@ pub(super) async fn handle_subscribe(
|
||||
});
|
||||
let resp = client
|
||||
.put(format!("{PUBSUB_API_BASE}/{sub}"))
|
||||
.bearer_auth(&pubsub_token)
|
||||
.bearer_auth(token)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&sub_body)
|
||||
.send()
|
||||
|
||||
Reference in New Issue
Block a user