892 lines
29 KiB
Rust
892 lines
29 KiB
Rust
use crate::api::APIErrorResponse;
|
|
use crate::{HostCommands, HostOverrideCommands, TableStyle};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::error::Error;
|
|
use std::fs;
|
|
use std::net::{Ipv4Addr, SocketAddrV4};
|
|
use comfy_table::modifiers::UTF8_ROUND_CORNERS;
|
|
use comfy_table::presets::UTF8_FULL;
|
|
use comfy_table::Table;
|
|
use url::Url;
|
|
|
|
pub async fn host_main(command: HostCommands, server: Url) -> Result<(), Box<dyn Error>> {
|
|
match command {
|
|
HostCommands::List { table_style } => list_hosts(server, table_style).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,
|
|
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,
|
|
},
|
|
}
|
|
}
|
|
|
|
#[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, table_style: TableStyle) -> 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("/v1/hosts?pageSize=5000")?)
|
|
.bearer_auth(token)
|
|
.send()
|
|
.await?;
|
|
|
|
if res.status().is_success() {
|
|
let resp: HostListResp = res.json().await?;
|
|
|
|
if resp.data.is_empty() {
|
|
println!("No hosts found");
|
|
return Ok(());
|
|
}
|
|
|
|
if matches!(table_style, TableStyle::List) {
|
|
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!();
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
let mut table = Table::new();
|
|
match table_style {
|
|
TableStyle::List => unreachable!(),
|
|
TableStyle::Basic => (),
|
|
TableStyle::Pretty => { table.load_preset(UTF8_FULL).apply_modifier(UTF8_ROUND_CORNERS) ; },
|
|
};
|
|
|
|
table.set_header(vec!["ID", "Name", "Organization ID", "Network ID", "Role ID", "IP Address", "Static Addresses", "Listen Port", "Type", "Blocked", "Last Seen"]);
|
|
|
|
for host in &resp.data {
|
|
table.add_row(vec![host.id.as_str(), &host.name, &host.organization_id, &host.network_id, &host.role_id, &host.ip_address, &host.static_addresses.iter().map(|u| u.to_string()).collect::<Vec<_>>().join(" "), &host.listen_port.to_string(), if host.is_lighthouse { "Lighthouse" } else if host.is_relay { "Relay" } else { "Host" }, if host.is_blocked { "true" } else { "false" }, &host.metadata.last_seen_at]);
|
|
}
|
|
|
|
println!("{table}");
|
|
|
|
|
|
} 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,
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
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("/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::new, |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);
|
|
|
|
let res = client
|
|
.post(server.join(&format!("/v1/hosts/{}/enrollment-code", id))?)
|
|
.header("content-length", 0)
|
|
.bearer_auth(token)
|
|
.send()
|
|
.await?;
|
|
|
|
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))?)
|
|
.header("Content-Length", "0")
|
|
.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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
Ok(())
|
|
}
|