diff --git a/src/appliance.rs b/src/appliance.rs index 18e0602..11bffc6 100644 --- a/src/appliance.rs +++ b/src/appliance.rs @@ -4,7 +4,10 @@ use anyhow::{anyhow, Context}; use clap::{Arg, ArgMatches, Command}; use tabled::{builder::Builder, settings::Style}; -use crate::edge::{new_client, Appliance, ApplianceHealthState, AppliancePortType, EdgeClient}; +use crate::edge::{ + new_client, Appliance, ApplianceHealthState, AppliancePortType, EdgeClient, RegionReference, + UpdateAppliancePayload, +}; use crate::{green, red}; pub(crate) fn subcommand() -> clap::Command { @@ -64,6 +67,21 @@ pub(crate) fn subcommand() -> clap::Command { .help("The name of the appliance"), ), ) + .subcommand( + Command::new("update") + .about("Update appliance settings") + .arg( + Arg::new("name") + .required(true) + .help("The name of the appliance"), + ) + .arg(Arg::new("region").long("region").help("The primary region")) + .arg( + Arg::new("secondary-region") + .long("secondary-region") + .help("The secondary region (use empty string \"\" to clear)"), + ), + ) } pub(crate) fn run(subcmd: &ArgMatches) { @@ -136,6 +154,18 @@ pub(crate) fn run(subcmd: &ArgMatches) { .expect("Appliance name is mandatory"); restart(client, name) } + Some(("update", args)) => { + let client = new_client(); + let name = args + .get_one::("name") + .map(|s| s.as_str()) + .expect("Appliance name is mandatory"); + let region = args.get_one::("region").map(|s| s.as_str()); + let secondary_region = args + .get_one::("secondary-region") + .map(|s| s.as_str()); + update(client, name, region, secondary_region) + } _ => unreachable!("subcommand_required prevents `None` or other options"), } } @@ -244,6 +274,10 @@ fn show(client: EdgeClient, name: &str) { println!("Product name; {}", appliance.kind); // TODO: Pretty-print println!("Serial number: {}", appliance.serial); println!("Group: {}", group_name); + println!("Region: {}", appliance.region.name); + if let Some(ref sec) = appliance.secondary_region { + println!("Secondary Region: {}", sec.name); + } println!( "Version (control): image={}, software={}", appliance @@ -419,3 +453,74 @@ fn get_appliance(client: &EdgeClient, name: &str) -> Appliance { } } } + +fn update( + client: EdgeClient, + name: &str, + region_arg: Option<&str>, + secondary_region_arg: Option<&str>, +) { + if region_arg.is_none() && secondary_region_arg.is_none() { + eprintln!("At least one of --region or --secondary-region must be specified"); + process::exit(1); + } + + let appliance = get_appliance(&client, name); + + let region = match region_arg { + Some(region_name) => { + let r = get_region(&client, region_name); + RegionReference { + id: r.id, + name: r.name, + } + } + None => appliance.region.clone(), + }; + + let secondary_region = match secondary_region_arg { + None => appliance.secondary_region.clone(), + Some("") => None, + Some(sec_name) => { + let r = get_region(&client, sec_name); + Some(RegionReference { + id: r.id, + name: r.name, + }) + } + }; + + let payload = UpdateAppliancePayload { + region, + secondary_region, + }; + + if let Err(e) = client.update_appliance(&appliance.id, payload) { + eprintln!("Failed to update appliance: {}", e); + process::exit(1); + } +} + +fn get_region(client: &EdgeClient, name: &str) -> crate::edge::Region { + let regions = match client.find_region(name) { + Ok(r) => r, + Err(e) => { + eprintln!("Failed to look up region: {}", e); + process::exit(1); + } + }; + + if regions.is_empty() { + eprintln!("Region '{}' not found", name); + process::exit(1); + } + + for region in regions { + if region.name == name { + return region; + } + } + + eprintln!("Region '{}' not found", name); + process::exit(1); +} diff --git a/src/edge.rs b/src/edge.rs index 82359c0..0b967cd 100644 --- a/src/edge.rs +++ b/src/edge.rs @@ -694,7 +694,8 @@ pub struct Appliance { pub last_registered_at: Option, // iso8601/rfc3339 pub health: Option, pub physical_ports: Vec, - // region { id, name } + pub region: RegionReference, + pub secondary_region: Option, #[serde(rename = "type")] pub kind: String, // owner is the group id @@ -1025,6 +1026,20 @@ pub struct NewRegion { pub external: ExternalRegionMode, } +#[derive(Debug, Deserialize, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct RegionReference { + pub id: String, + pub name: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdateAppliancePayload { + pub region: RegionReference, + pub secondary_region: Option, +} + #[derive(Debug, Clone)] pub enum ExternalRegionMode { Core = 0, @@ -1991,6 +2006,20 @@ impl EdgeClient { .map(|_| ()) } + pub fn update_appliance( + &self, + id: &str, + payload: UpdateAppliancePayload, + ) -> Result<(), EdgeError> { + self.client + .post(format!("{}/api/appliance/{}", self.url, id)) + .header("content-type", "application/json") + .json(&payload) + .send()? + .error_if_not_success() + .map(|_| ()) + } + pub fn list_regions(&self) -> Result, EdgeError> { #[derive(Debug, Deserialize)] struct RegionListResp {