From 3c6d15a54eb9bd832023b54135f3ec6fe70132d7 Mon Sep 17 00:00:00 2001 From: Matthias Tafelmeier Date: Mon, 20 Apr 2026 01:01:57 +0200 Subject: [PATCH] feat: update breeder with --id, --file, --force Update the breeder update command to match the new API endpoint. Accepts --id for breeder UUID, --file for new config YAML, and optional --force to clear trial history on incompatible updates. --- .github/workflows/ci.yml | 25 +++++++++++++-------- src/client.rs | 48 +++++++--------------------------------- src/lib.rs | 7 +++--- src/main.rs | 27 +++++++++++++--------- 4 files changed, 43 insertions(+), 64 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c62ca1..b3f516f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -254,17 +254,24 @@ jobs: echo "Testing: breeder show" $BINARY_PATH --hostname=localhost --port=4010 breeder show --id=550e8400-e29b-41d4-a716-446655440000 - # Create update test file - echo 'uuid: "550e8400-e29b-41d4-a716-446655440000"' > test_breeder_update.yml - echo 'name: "Updated Test Breeder"' >> test_breeder_update.yml - echo 'description: "Updated integration test breeder"' >> test_breeder_update.yml - echo 'config:' >> test_breeder_update.yml - echo ' setting1: "updated_value1"' >> test_breeder_update.yml - echo ' setting2: 100' >> test_breeder_update.yml - echo ' new_setting: "new_value"' >> test_breeder_update.yml + # Create update test file (config only, id passed via --id flag) + echo 'breeder:' > test_breeder_update.yml + echo ' type: "test_breeder"' >> test_breeder_update.yml + echo 'objectives:' >> test_breeder_update.yml + echo ' - name: "latency"' >> test_breeder_update.yml + echo 'settings:' >> test_breeder_update.yml + echo ' sysctl:' >> test_breeder_update.yml + echo ' net.core.somaxconn:' >> test_breeder_update.yml + echo ' constraints:' >> test_breeder_update.yml + echo ' - step: 100' >> test_breeder_update.yml + echo ' lower: 4096' >> test_breeder_update.yml + echo ' upper: 131072' >> test_breeder_update.yml echo "Testing: breeder update" - $BINARY_PATH --hostname=localhost --port=4010 breeder update --file=test_breeder_update.yml + $BINARY_PATH --hostname=localhost --port=4010 breeder update --id=550e8400-e29b-41d4-a716-446655440000 --file=test_breeder_update.yml + + echo "Testing: breeder update with --force" + $BINARY_PATH --hostname=localhost --port=4010 breeder update --id=550e8400-e29b-41d4-a716-446655440000 --file=test_breeder_update.yml --force # Test breeder purge (safe mode) echo "Testing: breeder purge (safe mode)" diff --git a/src/client.rs b/src/client.rs index a453576..256e8b6 100644 --- a/src/client.rs +++ b/src/client.rs @@ -143,23 +143,12 @@ impl GodonClient { } } - pub async fn update_breeder(&self, request: BreederUpdateRequest) -> ApiResponse { - let url = format!("{}/breeders/{}", self.base_url(), urlencoding::encode(&request.uuid)); - - let config: serde_json::Value = match serde_json::from_str(&request.config) { - Ok(c) => c, - Err(e) => return ApiResponse::error(format!("Config JSON parse error: {}", e)), - }; - - let body = serde_json::json!({ - "name": request.name, - "description": request.description, - "config": config - }); + pub async fn update_breeder(&self, uuid: &str, request: BreederUpdateRequest) -> ApiResponse { + let url = format!("{}/breeders/{}", self.base_url(), urlencoding::encode(uuid)); match self.client .put(&url) - .json(&body) + .json(&request) .send() .await { @@ -168,39 +157,18 @@ impl GodonClient { } } - pub async fn update_breeder_from_yaml(&self, yaml_content: &str) -> ApiResponse { - let yaml_data: serde_yaml::Value = match serde_yaml::from_str(yaml_content) { - Ok(d) => d, + pub async fn update_breeder_from_yaml(&self, uuid: &str, yaml_content: &str, force: bool) -> ApiResponse { + let config: serde_json::Value = match serde_yaml::from_str(yaml_content) { + Ok(c) => c, Err(e) => return ApiResponse::error(format!("YAML parse error: {}", e)), }; - let uuid = yaml_data.get("uuid") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); - - let name = yaml_data.get("name") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); - - let description = yaml_data.get("description") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); - - let config = yaml_data.get("config") - .map(|c| serde_json::to_string(c).unwrap_or_else(|_| "{}".to_string())) - .unwrap_or_else(|| "{}".to_string()); - let request = BreederUpdateRequest { - uuid, - name, - description, config, + force: if force { Some(true) } else { None }, }; - self.update_breeder(request).await + self.update_breeder(uuid, request).await } pub async fn delete_breeder(&self, uuid: &str, force: bool) -> ApiResponse { diff --git a/src/lib.rs b/src/lib.rs index 6960986..b928b09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,10 +31,9 @@ pub struct BreederCreateRequest { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BreederUpdateRequest { - pub uuid: String, - pub name: String, - pub description: String, - pub config: String, + pub config: serde_json::Value, + #[serde(skip_serializing_if = "Option::is_none")] + pub force: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/main.rs b/src/main.rs index 73e9cc5..002d508 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,8 +69,12 @@ enum BreederCommands { }, Update { + #[arg(long)] + id: String, #[arg(long)] file: PathBuf, + #[arg(long, default_value_t = false)] + force: bool, }, Stop { @@ -389,24 +393,25 @@ async fn handle_breeder_command(client: &GodonClient, cmd: BreederCommands, outp } } - BreederCommands::Update { file } => { + BreederCommands::Update { id, file, force } => { let content = match std::fs::read_to_string(&file) { Ok(c) => c, Err(e) => write_error(&format!("Failed to read file: {}", e)), }; - let response = client.update_breeder_from_yaml(&content).await; + let response = client.update_breeder_from_yaml(&id, &content, force).await; if response.success { if let Some(data) = response.data { - match data.get("id").and_then(|v| v.as_str()) { - Some(id) => { - if matches!(output, OutputFormat::Text) { - println!("Breeder updated successfully: {}", id); - } else { - format_output(&data, output); - } - } - None => write_error("Unexpected response format: missing 'id' field"), + if matches!(output, OutputFormat::Text) { + let breeder_id = data.get("breeder_id").and_then(|v| v.as_str()).unwrap_or(&id); + let trials_cleared = data.get("trials_cleared").and_then(|v| v.as_bool()).unwrap_or(false); + let history = data.get("config_history_entries").and_then(|v| v.as_u64()).unwrap_or(0); + println!("Breeder updated successfully:"); + println!(" ID: {}", breeder_id); + println!(" Trials cleared: {}", trials_cleared); + println!(" Config history entries: {}", history); + } else { + format_output(&data, output); } } } else {