2023-06-21 01:23:28 +00:00
|
|
|
use std::error::Error;
|
|
|
|
use std::fs;
|
|
|
|
use std::net::{Ipv4Addr, SocketAddrV4};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use url::{Url};
|
|
|
|
use crate::api::APIErrorResponse;
|
2023-06-22 01:03:11 +00:00
|
|
|
use crate::{HostCommands, HostOverrideCommands};
|
2023-06-21 01:23:28 +00:00
|
|
|
|
|
|
|
pub async fn host_main(command: HostCommands, server: Url) -> Result<(), Box<dyn Error>> {
|
|
|
|
match command {
|
|
|
|
HostCommands::List {} => list_hosts(server).await,
|
|
|
|
HostCommands::Create { name, network_id, role_id, ip_address, listen_port, lighthouse, relay, static_address } => create_host(name, network_id, role_id, ip_address, listen_port, lighthouse, relay, static_address, server).await,
|
|
|
|
HostCommands::Lookup { id } => get_host(id, server).await,
|
|
|
|
HostCommands::Delete { id } => delete_host(id, server).await,
|
|
|
|
HostCommands::Update { id, listen_port, static_address, name, ip, role } => update_host(id, listen_port, static_address, name, ip, role, server).await,
|
|
|
|
HostCommands::Block { id } => block_host(id, server).await,
|
2023-06-22 01:03:11 +00:00
|
|
|
HostCommands::Enroll { id } => enroll_host(id, server).await,
|
|
|
|
HostCommands::Overrides { command } => match command {
|
|
|
|
HostOverrideCommands::List { id } => list_overrides(id, server).await,
|
|
|
|
HostOverrideCommands::Set { id, key, boolean, string, numeric } => set_override(id, key, boolean, numeric, string, server).await,
|
|
|
|
HostOverrideCommands::Unset { id, key } => unset_override(id, key, server).await
|
|
|
|
}
|
2023-06-21 01:23:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
pub struct HostListResp {
|
|
|
|
pub data: Vec<Host>
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct HostMetadata {
|
|
|
|
#[serde(rename = "lastSeenAt")]
|
|
|
|
pub last_seen_at: String,
|
|
|
|
pub version: String,
|
|
|
|
pub platform: String,
|
|
|
|
#[serde(rename = "updateAvailable")]
|
|
|
|
pub update_available: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct Host {
|
|
|
|
pub id: String,
|
|
|
|
#[serde(rename = "organizationID")]
|
|
|
|
pub organization_id: String,
|
|
|
|
#[serde(rename = "networkID")]
|
|
|
|
pub network_id: String,
|
|
|
|
#[serde(rename = "roleID")]
|
|
|
|
pub role_id: String,
|
|
|
|
pub name: String,
|
|
|
|
#[serde(rename = "ipAddress")]
|
|
|
|
pub ip_address: String,
|
|
|
|
#[serde(rename = "staticAddresses")]
|
|
|
|
pub static_addresses: Vec<SocketAddrV4>,
|
|
|
|
#[serde(rename = "listenPort")]
|
|
|
|
pub listen_port: i64,
|
|
|
|
#[serde(rename = "isLighthouse")]
|
|
|
|
pub is_lighthouse: bool,
|
|
|
|
#[serde(rename = "isRelay")]
|
|
|
|
pub is_relay: bool,
|
|
|
|
#[serde(rename = "createdAt")]
|
|
|
|
pub created_at: String,
|
|
|
|
#[serde(rename = "isBlocked")]
|
|
|
|
pub is_blocked: bool,
|
|
|
|
pub metadata: HostMetadata,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn list_hosts(server: Url) -> Result<(), Box<dyn Error>> {
|
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
|
|
|
// load session token
|
|
|
|
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
|
|
|
let session_token = fs::read_to_string(&sess_token_store)?;
|
|
|
|
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
|
|
|
let auth_token = fs::read_to_string(&auth_token_store)?;
|
|
|
|
|
|
|
|
let token = format!("{} {}", session_token, auth_token);
|
|
|
|
|
2023-07-14 23:12:45 +00:00
|
|
|
let res = client.get(server.join("/v1/hosts?pageSize=5000")?).bearer_auth(token).send().await?;
|
2023-06-21 01:23:28 +00:00
|
|
|
|
|
|
|
if res.status().is_success() {
|
|
|
|
let resp: HostListResp = res.json().await?;
|
|
|
|
|
|
|
|
for host in &resp.data {
|
|
|
|
println!(" Host: {} ({})", host.name, host.id);
|
|
|
|
println!(" Organization: {}", host.organization_id);
|
|
|
|
println!(" Network: {}", host.network_id);
|
|
|
|
println!(" Role: {}", host.role_id);
|
|
|
|
println!(" IP Address: {}", host.ip_address);
|
|
|
|
println!(" Static Addresses: {}", host.static_addresses.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(", "));
|
|
|
|
println!(" Listen Port: {}", host.listen_port);
|
|
|
|
println!(" Type: {}", if host.is_lighthouse { "Lighthouse" } else if host.is_relay { "Relay" } else { "Host" } );
|
|
|
|
println!(" Blocked: {}", host.is_blocked);
|
|
|
|
println!(" Last Seen: {}", host.metadata.last_seen_at);
|
|
|
|
println!(" Client Version: {}", host.metadata.version);
|
|
|
|
println!(" Platform: {}", host.metadata.platform);
|
|
|
|
println!("Client Update Available: {}", host.metadata.update_available);
|
|
|
|
println!(" Created: {}", host.created_at);
|
|
|
|
println!();
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.data.is_empty() {
|
|
|
|
println!("No hosts found");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let resp: APIErrorResponse = res.json().await?;
|
|
|
|
|
|
|
|
eprintln!("[error] Error listing hosts: {} {}", resp.errors[0].code, resp.errors[0].message);
|
|
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct HostCreateBody {
|
|
|
|
pub name: String,
|
|
|
|
#[serde(rename = "networkID")]
|
|
|
|
pub network_id: String,
|
|
|
|
#[serde(rename = "roleID")]
|
|
|
|
pub role_id: String,
|
|
|
|
#[serde(rename = "ipAddress")]
|
|
|
|
pub ip_address: Ipv4Addr,
|
|
|
|
#[serde(rename = "listenPort")]
|
|
|
|
pub listen_port: u16,
|
|
|
|
#[serde(rename = "isLighthouse")]
|
|
|
|
pub is_lighthouse: bool,
|
|
|
|
#[serde(rename = "isRelay")]
|
|
|
|
pub is_relay: bool,
|
|
|
|
#[serde(rename = "staticAddresses")]
|
|
|
|
pub static_addresses: Vec<SocketAddrV4>,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct HostGetMetadata {}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct HostGetResponse {
|
|
|
|
pub data: Host,
|
|
|
|
pub metadata: HostGetMetadata,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn create_host(name: String, network_id: String, role_id: String, ip_address: Ipv4Addr, listen_port: Option<u16>, lighthouse: bool, relay: bool, static_address: Option<SocketAddrV4>, server: Url) -> Result<(), Box<dyn Error>> {
|
|
|
|
if lighthouse && relay {
|
|
|
|
eprintln!("[error] Error creating host: a host cannot be both a lighthouse and a relay at the same time");
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lighthouse || relay) && static_address.is_none() {
|
|
|
|
eprintln!("[error] Error creating host: a relay or lighthouse must have a static IP");
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lighthouse || relay) && listen_port.is_none() {
|
|
|
|
eprintln!("[error] Error creating host: a relay or lighthouse must have a listen port");
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
|
|
|
// load session token
|
|
|
|
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
|
|
|
let session_token = fs::read_to_string(&sess_token_store)?;
|
|
|
|
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
|
|
|
let auth_token = fs::read_to_string(&auth_token_store)?;
|
|
|
|
|
|
|
|
let token = format!("{} {}", session_token, auth_token);
|
|
|
|
|
|
|
|
let res = client.post(server.join(&format!("/v1/hosts"))?).json(&HostCreateBody {
|
|
|
|
name,
|
|
|
|
network_id,
|
|
|
|
role_id,
|
|
|
|
ip_address,
|
|
|
|
listen_port: listen_port.unwrap_or(0),
|
|
|
|
is_lighthouse: lighthouse,
|
|
|
|
is_relay: relay,
|
|
|
|
static_addresses: static_address.map_or(vec![], |u| vec![u]),
|
|
|
|
}).bearer_auth(token).send().await?;
|
|
|
|
|
|
|
|
if res.status().is_success() {
|
|
|
|
let host: Host = res.json::<HostGetResponse>().await?.data;
|
|
|
|
|
|
|
|
println!(" Host: {} ({})", host.name, host.id);
|
|
|
|
println!(" Organization: {}", host.organization_id);
|
|
|
|
println!(" Network: {}", host.network_id);
|
|
|
|
println!(" Role: {}", host.role_id);
|
|
|
|
println!(" IP Address: {}", host.ip_address);
|
|
|
|
println!(" Static Addresses: {}", host.static_addresses.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(", "));
|
|
|
|
println!(" Listen Port: {}", host.listen_port);
|
|
|
|
println!(" Type: {}", if host.is_lighthouse { "Lighthouse" } else if host.is_relay { "Relay" } else { "Host" } );
|
|
|
|
println!(" Blocked: {}", host.is_blocked);
|
|
|
|
println!(" Last Seen: {}", host.metadata.last_seen_at);
|
|
|
|
println!(" Client Version: {}", host.metadata.version);
|
|
|
|
println!(" Platform: {}", host.metadata.platform);
|
|
|
|
println!("Client Update Available: {}", host.metadata.update_available);
|
|
|
|
println!(" Created: {}", host.created_at);
|
|
|
|
println!();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
let resp: APIErrorResponse = res.json().await?;
|
|
|
|
|
|
|
|
eprintln!("[error] Error creating host: {} {}", resp.errors[0].code, resp.errors[0].message);
|
|
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_host(id: String, server: Url) -> Result<(), Box<dyn Error>> {
|
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
|
|
|
// load session token
|
|
|
|
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
|
|
|
let session_token = fs::read_to_string(&sess_token_store)?;
|
|
|
|
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
|
|
|
let auth_token = fs::read_to_string(&auth_token_store)?;
|
|
|
|
|
|
|
|
let token = format!("{} {}", session_token, auth_token);
|
|
|
|
|
|
|
|
let res = client.get(server.join(&format!("/v1/hosts/{}", id))?).bearer_auth(token).send().await?;
|
|
|
|
|
|
|
|
if res.status().is_success() {
|
|
|
|
let host: Host = res.json::<HostGetResponse>().await?.data;
|
|
|
|
|
|
|
|
println!(" Host: {} ({})", host.name, host.id);
|
|
|
|
println!(" Organization: {}", host.organization_id);
|
|
|
|
println!(" Network: {}", host.network_id);
|
|
|
|
println!(" Role: {}", host.role_id);
|
|
|
|
println!(" IP Address: {}", host.ip_address);
|
|
|
|
println!(" Static Addresses: {}", host.static_addresses.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(", "));
|
|
|
|
println!(" Listen Port: {}", host.listen_port);
|
|
|
|
println!(" Type: {}", if host.is_lighthouse { "Lighthouse" } else if host.is_relay { "Relay" } else { "Host" } );
|
|
|
|
println!(" Blocked: {}", host.is_blocked);
|
|
|
|
println!(" Last Seen: {}", host.metadata.last_seen_at);
|
|
|
|
println!(" Client Version: {}", host.metadata.version);
|
|
|
|
println!(" Platform: {}", host.metadata.platform);
|
|
|
|
println!("Client Update Available: {}", host.metadata.update_available);
|
|
|
|
println!(" Created: {}", host.created_at);
|
|
|
|
println!();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
let resp: APIErrorResponse = res.json().await?;
|
|
|
|
|
|
|
|
eprintln!("[error] Error listing hosts: {} {}", resp.errors[0].code, resp.errors[0].message);
|
|
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn delete_host(id: String, server: Url) -> Result<(), Box<dyn Error>> {
|
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
|
|
|
// load session token
|
|
|
|
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
|
|
|
let session_token = fs::read_to_string(&sess_token_store)?;
|
|
|
|
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
|
|
|
let auth_token = fs::read_to_string(&auth_token_store)?;
|
|
|
|
|
|
|
|
let token = format!("{} {}", session_token, auth_token);
|
|
|
|
|
|
|
|
let res = client.delete(server.join(&format!("/v1/hosts/{}", id))?).bearer_auth(token).send().await?;
|
|
|
|
|
|
|
|
if res.status().is_success() {
|
|
|
|
println!("Host removed");
|
|
|
|
} else {
|
|
|
|
let resp: APIErrorResponse = res.json().await?;
|
|
|
|
|
|
|
|
eprintln!("[error] Error removing host: {} {}", resp.errors[0].code, resp.errors[0].message);
|
|
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct HostUpdateBody {
|
|
|
|
#[serde(rename = "listenPort")]
|
|
|
|
pub listen_port: u16,
|
|
|
|
#[serde(rename = "staticAddresses")]
|
|
|
|
pub static_addresses: Vec<SocketAddrV4>,
|
|
|
|
pub name: Option<String>,
|
|
|
|
pub ip: Option<Ipv4Addr>,
|
|
|
|
pub role: Option<String>
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn update_host(id: String, listen_port: Option<u16>, static_address: Option<SocketAddrV4>, name: Option<String>, ip: Option<Ipv4Addr>, role: Option<String>, server: Url) -> Result<(), Box<dyn Error>> {
|
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
|
|
|
// load session token
|
|
|
|
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
|
|
|
let session_token = fs::read_to_string(&sess_token_store)?;
|
|
|
|
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
|
|
|
let auth_token = fs::read_to_string(&auth_token_store)?;
|
|
|
|
|
|
|
|
let token = format!("{} {}", session_token, auth_token);
|
|
|
|
|
|
|
|
let res = client.put(server.join(&format!("/v1/hosts/{}?extension=extended_hosts", id))?).json(&HostUpdateBody {
|
|
|
|
listen_port: listen_port.unwrap_or(0),
|
|
|
|
static_addresses: static_address.map_or_else(|| vec![], |u| vec![u]),
|
|
|
|
name,
|
|
|
|
ip,
|
|
|
|
role
|
|
|
|
}).bearer_auth(token).send().await?;
|
|
|
|
|
|
|
|
if res.status().is_success() {
|
|
|
|
let host: Host = res.json::<HostGetResponse>().await?.data;
|
|
|
|
|
|
|
|
println!(" Host: {} ({})", host.name, host.id);
|
|
|
|
println!(" Organization: {}", host.organization_id);
|
|
|
|
println!(" Network: {}", host.network_id);
|
|
|
|
println!(" Role: {}", host.role_id);
|
|
|
|
println!(" IP Address: {}", host.ip_address);
|
|
|
|
println!(" Static Addresses: {}", host.static_addresses.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(", "));
|
|
|
|
println!(" Listen Port: {}", host.listen_port);
|
|
|
|
println!(" Type: {}", if host.is_lighthouse { "Lighthouse" } else if host.is_relay { "Relay" } else { "Host" } );
|
|
|
|
println!(" Blocked: {}", host.is_blocked);
|
|
|
|
println!(" Last Seen: {}", host.metadata.last_seen_at);
|
|
|
|
println!(" Client Version: {}", host.metadata.version);
|
|
|
|
println!(" Platform: {}", host.metadata.platform);
|
|
|
|
println!("Client Update Available: {}", host.metadata.update_available);
|
|
|
|
println!(" Created: {}", host.created_at);
|
|
|
|
println!();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
let resp: APIErrorResponse = res.json().await?;
|
|
|
|
|
|
|
|
eprintln!("[error] Error updating host: {} {}", resp.errors[0].code, resp.errors[0].message);
|
|
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct EnrollmentCodeResponseMetadata {}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct EnrollmentCode {
|
|
|
|
pub code: String,
|
|
|
|
#[serde(rename = "lifetimeSeconds")]
|
|
|
|
pub lifetime_seconds: i64,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct EnrollmentResponseData {
|
|
|
|
#[serde(rename = "enrollmentCode")]
|
|
|
|
pub enrollment_code: EnrollmentCode,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct EnrollmentResponse {
|
|
|
|
pub data: EnrollmentResponseData,
|
|
|
|
pub metadata: EnrollmentCodeResponseMetadata,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn enroll_host(id: String, server: Url) -> Result<(), Box<dyn Error>> {
|
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
|
|
|
// load session token
|
|
|
|
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
|
|
|
let session_token = fs::read_to_string(&sess_token_store)?;
|
|
|
|
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
|
|
|
let auth_token = fs::read_to_string(&auth_token_store)?;
|
|
|
|
|
|
|
|
let token = format!("{} {}", session_token, auth_token);
|
|
|
|
|
2023-06-24 00:10:19 +00:00
|
|
|
let res = client.post(server.join(&format!("/v1/hosts/{}/enrollment-code", id))?).header("content-length", 0).bearer_auth(token).send().await?;
|
2023-06-21 01:23:28 +00:00
|
|
|
|
|
|
|
if res.status().is_success() {
|
|
|
|
let resp: EnrollmentResponse = res.json().await?;
|
|
|
|
|
|
|
|
println!("Enrollment code generated. Enroll the host with the following code: {}", resp.data.enrollment_code.code);
|
|
|
|
println!("This code will be valid for {} seconds, at which point you will need to generate a new code", resp.data.enrollment_code.lifetime_seconds);
|
|
|
|
println!("If this host is blocked, a successful re-enrollment will unblock it.");
|
|
|
|
} else {
|
|
|
|
let resp: APIErrorResponse = res.json().await?;
|
|
|
|
|
|
|
|
eprintln!("[error] Error blocking host: {} {}", resp.errors[0].code, resp.errors[0].message);
|
|
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn block_host(id: String, server: Url) -> Result<(), Box<dyn Error>> {
|
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
|
|
|
// load session token
|
|
|
|
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
|
|
|
let session_token = fs::read_to_string(&sess_token_store)?;
|
|
|
|
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
|
|
|
let auth_token = fs::read_to_string(&auth_token_store)?;
|
|
|
|
|
|
|
|
let token = format!("{} {}", session_token, auth_token);
|
|
|
|
|
|
|
|
let res = client.post(server.join(&format!("/v1/hosts/{}/block", id))?).bearer_auth(token).send().await?;
|
|
|
|
|
|
|
|
if res.status().is_success() {
|
|
|
|
println!("Host blocked. To unblock it, re-enroll the host.");
|
|
|
|
} else {
|
|
|
|
let resp: APIErrorResponse = res.json().await?;
|
|
|
|
|
|
|
|
eprintln!("[error] Error blocking host: {} {}", resp.errors[0].code, resp.errors[0].message);
|
|
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
2023-06-22 01:03:11 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct HostConfigOverrideResponse {
|
|
|
|
pub data: HostConfigOverrideData
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct HostConfigOverrideData {
|
|
|
|
pub overrides: Vec<HostConfigOverrideDataOverride>
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct HostConfigOverrideDataOverride {
|
|
|
|
pub key: String,
|
|
|
|
pub value: HostConfigOverrideDataOverrideValue
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(untagged)]
|
|
|
|
pub enum HostConfigOverrideDataOverrideValue {
|
|
|
|
Boolean(bool),
|
|
|
|
Numeric(i64),
|
|
|
|
Other(String)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn list_overrides(id: String, server: Url) -> Result<(), Box<dyn Error>> {
|
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
|
|
|
// load session token
|
|
|
|
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
|
|
|
let session_token = fs::read_to_string(&sess_token_store)?;
|
|
|
|
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
|
|
|
let auth_token = fs::read_to_string(&auth_token_store)?;
|
|
|
|
|
|
|
|
let token = format!("{} {}", session_token, auth_token);
|
|
|
|
|
|
|
|
let res = client.get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token).send().await?;
|
|
|
|
|
|
|
|
if res.status().is_success() {
|
|
|
|
let resp: HostConfigOverrideResponse = res.json().await?;
|
|
|
|
|
|
|
|
for c_override in &resp.data.overrides {
|
|
|
|
println!(" Key: {}", c_override.key);
|
|
|
|
println!("Value: {}", match &c_override.value {
|
|
|
|
HostConfigOverrideDataOverrideValue::Boolean(v) => format!("bool:{}", v),
|
|
|
|
HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v),
|
|
|
|
HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.data.overrides.is_empty() {
|
|
|
|
println!("No overrides found");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let resp: APIErrorResponse = res.json().await?;
|
|
|
|
|
|
|
|
eprintln!("[error] Error looking up config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
|
|
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct SetOverrideRequest {
|
|
|
|
pub overrides: Vec<HostConfigOverrideDataOverride>
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn set_override(id: String, key: String, boolean: Option<bool>, numeric: Option<i64>, other: Option<String>, server: Url) -> Result<(), Box<dyn Error>> {
|
|
|
|
if boolean.is_none() && numeric.is_none() && other.is_none() {
|
|
|
|
eprintln!("[error] no value provided: you must provide at least --boolean, --numeric, or --string");
|
|
|
|
std::process::exit(1);
|
|
|
|
} else if boolean.is_some() && numeric.is_some() || boolean.is_some() && other.is_some() || numeric.is_some() && other.is_some() {
|
|
|
|
eprintln!("[error] multiple values provided: you must provide only one of --boolean, --numeric, or --string");
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
let val;
|
|
|
|
|
|
|
|
if let Some(v) = boolean {
|
|
|
|
val = HostConfigOverrideDataOverrideValue::Boolean(v);
|
|
|
|
} else if let Some(v) = numeric {
|
|
|
|
val = HostConfigOverrideDataOverrideValue::Numeric(v);
|
|
|
|
} else if let Some(v) = other {
|
|
|
|
val = HostConfigOverrideDataOverrideValue::Other(v);
|
|
|
|
} else {
|
|
|
|
unreachable!();
|
|
|
|
}
|
|
|
|
|
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
|
|
|
// load session token
|
|
|
|
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
|
|
|
let session_token = fs::read_to_string(&sess_token_store)?;
|
|
|
|
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
|
|
|
let auth_token = fs::read_to_string(&auth_token_store)?;
|
|
|
|
|
|
|
|
let token = format!("{} {}", session_token, auth_token);
|
|
|
|
|
|
|
|
let res = client.get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).send().await?;
|
|
|
|
|
|
|
|
if res.status().is_success() {
|
|
|
|
let resp: HostConfigOverrideResponse = res.json().await?;
|
|
|
|
|
|
|
|
let mut others: Vec<HostConfigOverrideDataOverride> = vec![];
|
|
|
|
|
|
|
|
for c_override in resp.data.overrides {
|
|
|
|
if c_override.key != key {
|
|
|
|
others.push(c_override);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
others.push(HostConfigOverrideDataOverride {
|
|
|
|
key,
|
|
|
|
value: val,
|
|
|
|
});
|
|
|
|
|
|
|
|
let res = client.put(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).json(&SetOverrideRequest {
|
|
|
|
overrides: others,
|
|
|
|
}).send().await?;
|
|
|
|
|
|
|
|
if res.status().is_success() {
|
|
|
|
let resp: HostConfigOverrideResponse = res.json().await?;
|
|
|
|
|
|
|
|
for c_override in &resp.data.overrides {
|
|
|
|
println!(" Key: {}", c_override.key);
|
|
|
|
println!("Value: {}", match &c_override.value {
|
|
|
|
HostConfigOverrideDataOverrideValue::Boolean(v) => format!("bool:{}", v),
|
|
|
|
HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v),
|
|
|
|
HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.data.overrides.is_empty() {
|
|
|
|
println!("No overrides found");
|
|
|
|
}
|
|
|
|
|
|
|
|
println!("Override set successfully");
|
|
|
|
} else {
|
|
|
|
let resp: APIErrorResponse = res.json().await?;
|
|
|
|
|
|
|
|
eprintln!("[error] Error setting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
|
|
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let resp: APIErrorResponse = res.json().await?;
|
|
|
|
|
|
|
|
eprintln!("[error] Error setting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
|
|
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn unset_override(id: String, key: String, server: Url) -> Result<(), Box<dyn Error>> {
|
|
|
|
let client = reqwest::Client::new();
|
|
|
|
|
|
|
|
// load session token
|
|
|
|
let sess_token_store = dirs::config_dir().unwrap().join("tfcli-session.token");
|
|
|
|
let session_token = fs::read_to_string(&sess_token_store)?;
|
|
|
|
let auth_token_store = dirs::config_dir().unwrap().join("tfcli-auth.token");
|
|
|
|
let auth_token = fs::read_to_string(&auth_token_store)?;
|
|
|
|
|
|
|
|
let token = format!("{} {}", session_token, auth_token);
|
|
|
|
|
|
|
|
let res = client.get(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).send().await?;
|
|
|
|
|
|
|
|
if res.status().is_success() {
|
|
|
|
let resp: HostConfigOverrideResponse = res.json().await?;
|
|
|
|
|
|
|
|
let mut others: Vec<HostConfigOverrideDataOverride> = vec![];
|
|
|
|
|
|
|
|
for c_override in resp.data.overrides {
|
|
|
|
if c_override.key != key {
|
|
|
|
others.push(c_override);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let res = client.put(server.join(&format!("/v1/hosts/{}/config-overrides", id))?).bearer_auth(token.clone()).json(&SetOverrideRequest {
|
|
|
|
overrides: others,
|
|
|
|
}).send().await?;
|
|
|
|
|
|
|
|
if res.status().is_success() {
|
|
|
|
let resp: HostConfigOverrideResponse = res.json().await?;
|
|
|
|
|
|
|
|
for c_override in &resp.data.overrides {
|
|
|
|
println!(" Key: {}", c_override.key);
|
|
|
|
println!("Value: {}", match &c_override.value {
|
|
|
|
HostConfigOverrideDataOverrideValue::Boolean(v) => format!("bool:{}", v),
|
|
|
|
HostConfigOverrideDataOverrideValue::Numeric(v) => format!("numeric:{}", v),
|
|
|
|
HostConfigOverrideDataOverrideValue::Other(v) => format!("string:{}", v)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.data.overrides.is_empty() {
|
|
|
|
println!("No overrides found");
|
|
|
|
}
|
|
|
|
|
|
|
|
println!("Override unset successfully");
|
|
|
|
} else {
|
|
|
|
let resp: APIErrorResponse = res.json().await?;
|
|
|
|
|
|
|
|
eprintln!("[error] Error unsetting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
|
|
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let resp: APIErrorResponse = res.json().await?;
|
|
|
|
|
|
|
|
eprintln!("[error] Error unsetting config overrides: {} {}", resp.errors[0].code, resp.errors[0].message);
|
|
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
2023-06-21 01:23:28 +00:00
|
|
|
Ok(())
|
2023-06-24 00:10:19 +00:00
|
|
|
}
|