use std::collections::HashMap;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::client::HttpClient;
use crate::error::Result;
use crate::types::{AgentService, HealthCheck, WriteMeta};
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AgentMember {
pub name: String,
pub addr: String,
pub port: u16,
#[serde(default)]
pub tags: HashMap<String, String>,
pub status: i32,
pub protocol_min: u8,
pub protocol_max: u8,
pub protocol_cur: u8,
pub delegate_min: u8,
pub delegate_max: u8,
pub delegate_cur: u8,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AgentSelf {
pub config: HashMap<String, serde_json::Value>,
#[serde(default)]
pub coord: Option<serde_json::Value>,
pub member: AgentMember,
#[serde(default)]
pub stats: HashMap<String, HashMap<String, String>>,
#[serde(default)]
pub meta: HashMap<String, String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AgentServiceRegistration {
#[serde(rename = "ID")]
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub name: String,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<String>,
#[serde(default)]
pub enable_tag_override: bool,
#[serde(default)]
#[serde(skip_serializing_if = "HashMap::is_empty")]
pub meta: HashMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub check: Option<AgentServiceCheck>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub checks: Vec<AgentServiceCheck>,
}
impl AgentServiceRegistration {
pub fn new(name: &str) -> Self {
Self {
id: None,
name: name.to_string(),
tags: Vec::new(),
port: None,
address: None,
enable_tag_override: false,
meta: HashMap::new(),
check: None,
checks: Vec::new(),
}
}
pub fn with_id(mut self, id: &str) -> Self {
self.id = Some(id.to_string());
self
}
pub fn with_address(mut self, address: &str) -> Self {
self.address = Some(address.to_string());
self
}
pub fn with_port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
self.tags = tags;
self
}
pub fn with_meta(mut self, key: &str, value: &str) -> Self {
self.meta.insert(key.to_string(), value.to_string());
self
}
pub fn with_check(mut self, check: AgentServiceCheck) -> Self {
self.check = Some(check);
self
}
pub fn add_check(mut self, check: AgentServiceCheck) -> Self {
self.checks.push(check);
self
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AgentServiceCheck {
#[serde(rename = "CheckID")]
#[serde(skip_serializing_if = "Option::is_none")]
pub check_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub script: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub args: Option<Vec<String>>,
#[serde(rename = "DockerContainerID")]
#[serde(skip_serializing_if = "Option::is_none")]
pub docker_container_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shell: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub interval: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<String>,
#[serde(rename = "TTL")]
#[serde(skip_serializing_if = "Option::is_none")]
pub ttl: Option<String>,
#[serde(rename = "HTTP")]
#[serde(skip_serializing_if = "Option::is_none")]
pub http: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub method: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub header: Option<HashMap<String, Vec<String>>>,
#[serde(rename = "TCP")]
#[serde(skip_serializing_if = "Option::is_none")]
pub tcp: Option<String>,
#[serde(rename = "GRPC")]
#[serde(skip_serializing_if = "Option::is_none")]
pub grpc: Option<String>,
#[serde(rename = "GRPCUseTLS")]
#[serde(skip_serializing_if = "Option::is_none")]
pub grpc_use_tls: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
#[serde(rename = "TLSSkipVerify")]
#[serde(skip_serializing_if = "Option::is_none")]
pub tls_skip_verify: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deregister_critical_service_after: Option<String>,
}
impl AgentServiceCheck {
pub fn http(url: &str, interval: &str) -> Self {
Self {
check_id: None,
name: None,
script: None,
args: None,
docker_container_id: None,
shell: None,
interval: Some(interval.to_string()),
timeout: None,
ttl: None,
http: Some(url.to_string()),
method: None,
header: None,
tcp: None,
grpc: None,
grpc_use_tls: None,
status: None,
notes: None,
tls_skip_verify: None,
deregister_critical_service_after: None,
}
}
pub fn tcp(address: &str, interval: &str) -> Self {
Self {
check_id: None,
name: None,
script: None,
args: None,
docker_container_id: None,
shell: None,
interval: Some(interval.to_string()),
timeout: None,
ttl: None,
http: None,
method: None,
header: None,
tcp: Some(address.to_string()),
grpc: None,
grpc_use_tls: None,
status: None,
notes: None,
tls_skip_verify: None,
deregister_critical_service_after: None,
}
}
pub fn ttl(ttl: &str) -> Self {
Self {
check_id: None,
name: None,
script: None,
args: None,
docker_container_id: None,
shell: None,
interval: None,
timeout: None,
ttl: Some(ttl.to_string()),
http: None,
method: None,
header: None,
tcp: None,
grpc: None,
grpc_use_tls: None,
status: None,
notes: None,
tls_skip_verify: None,
deregister_critical_service_after: None,
}
}
pub fn grpc(address: &str, interval: &str) -> Self {
Self {
check_id: None,
name: None,
script: None,
args: None,
docker_container_id: None,
shell: None,
interval: Some(interval.to_string()),
timeout: None,
ttl: None,
http: None,
method: None,
header: None,
tcp: None,
grpc: Some(address.to_string()),
grpc_use_tls: None,
status: None,
notes: None,
tls_skip_verify: None,
deregister_critical_service_after: None,
}
}
pub fn with_name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
pub fn with_timeout(mut self, timeout: &str) -> Self {
self.timeout = Some(timeout.to_string());
self
}
pub fn with_deregister_after(mut self, duration: &str) -> Self {
self.deregister_critical_service_after = Some(duration.to_string());
self
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct AgentCheckRegistration {
#[serde(rename = "ID")]
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
#[serde(rename = "ServiceID")]
#[serde(skip_serializing_if = "Option::is_none")]
pub service_id: Option<String>,
#[serde(flatten)]
pub check: AgentServiceCheck,
}
pub struct Agent {
client: Arc<HttpClient>,
}
impl Agent {
pub fn new(client: Arc<HttpClient>) -> Self {
Self { client }
}
pub async fn self_info(&self) -> Result<AgentSelf> {
let builder = self.client.get("/v1/agent/self");
self.client.execute_json(builder).await
}
pub async fn node_name(&self) -> Result<String> {
let info = self.self_info().await?;
Ok(info.member.name)
}
pub async fn services(&self) -> Result<HashMap<String, AgentService>> {
let builder = self.client.get("/v1/agent/services");
self.client.execute_json(builder).await
}
pub async fn service(&self, service_id: &str) -> Result<AgentService> {
let path = format!("/v1/agent/service/{}", service_id);
let builder = self.client.get(&path);
self.client.execute_json(builder).await
}
pub async fn checks(&self) -> Result<HashMap<String, HealthCheck>> {
let builder = self.client.get("/v1/agent/checks");
self.client.execute_json(builder).await
}
pub async fn members(&self, wan: bool) -> Result<Vec<AgentMember>> {
let path = if wan {
"/v1/agent/members?wan=1"
} else {
"/v1/agent/members"
};
let builder = self.client.get(path);
self.client.execute_json(builder).await
}
pub async fn service_register(&self, registration: &AgentServiceRegistration) -> Result<WriteMeta> {
let builder = self.client.put("/v1/agent/service/register").json(registration);
self.client.write_empty(builder).await
}
pub async fn service_deregister(&self, service_id: &str) -> Result<WriteMeta> {
let path = format!("/v1/agent/service/deregister/{}", service_id);
let builder = self.client.put(&path);
self.client.write_empty(builder).await
}
pub async fn check_register(&self, registration: &AgentCheckRegistration) -> Result<WriteMeta> {
let builder = self.client.put("/v1/agent/check/register").json(registration);
self.client.write_empty(builder).await
}
pub async fn check_deregister(&self, check_id: &str) -> Result<WriteMeta> {
let path = format!("/v1/agent/check/deregister/{}", check_id);
let builder = self.client.put(&path);
self.client.write_empty(builder).await
}
pub async fn pass_ttl(&self, check_id: &str, note: Option<&str>) -> Result<WriteMeta> {
let mut path = format!("/v1/agent/check/pass/{}", check_id);
if let Some(n) = note {
path.push_str(&format!("?note={}", urlencoding::encode(n)));
}
let builder = self.client.put(&path);
self.client.write_empty(builder).await
}
pub async fn warn_ttl(&self, check_id: &str, note: Option<&str>) -> Result<WriteMeta> {
let mut path = format!("/v1/agent/check/warn/{}", check_id);
if let Some(n) = note {
path.push_str(&format!("?note={}", urlencoding::encode(n)));
}
let builder = self.client.put(&path);
self.client.write_empty(builder).await
}
pub async fn fail_ttl(&self, check_id: &str, note: Option<&str>) -> Result<WriteMeta> {
let mut path = format!("/v1/agent/check/fail/{}", check_id);
if let Some(n) = note {
path.push_str(&format!("?note={}", urlencoding::encode(n)));
}
let builder = self.client.put(&path);
self.client.write_empty(builder).await
}
pub async fn update_ttl(&self, check_id: &str, output: &str, status: &str) -> Result<WriteMeta> {
let path = format!("/v1/agent/check/update/{}", check_id);
let body = serde_json::json!({
"Output": output,
"Status": status
});
let builder = self.client.put(&path).json(&body);
self.client.write_empty(builder).await
}
pub async fn enable_service_maintenance(&self, service_id: &str, reason: Option<&str>) -> Result<WriteMeta> {
let mut path = format!("/v1/agent/service/maintenance/{}?enable=true", service_id);
if let Some(r) = reason {
path.push_str(&format!("&reason={}", urlencoding::encode(r)));
}
let builder = self.client.put(&path);
self.client.write_empty(builder).await
}
pub async fn disable_service_maintenance(&self, service_id: &str) -> Result<WriteMeta> {
let path = format!("/v1/agent/service/maintenance/{}?enable=false", service_id);
let builder = self.client.put(&path);
self.client.write_empty(builder).await
}
pub async fn enable_node_maintenance(&self, reason: Option<&str>) -> Result<WriteMeta> {
let mut path = "/v1/agent/maintenance?enable=true".to_string();
if let Some(r) = reason {
path.push_str(&format!("&reason={}", urlencoding::encode(r)));
}
let builder = self.client.put(&path);
self.client.write_empty(builder).await
}
pub async fn disable_node_maintenance(&self) -> Result<WriteMeta> {
let builder = self.client.put("/v1/agent/maintenance?enable=false");
self.client.write_empty(builder).await
}
pub async fn force_leave(&self, node: &str) -> Result<WriteMeta> {
let path = format!("/v1/agent/force-leave/{}", node);
let builder = self.client.put(&path);
self.client.write_empty(builder).await
}
pub async fn join(&self, address: &str, wan: bool) -> Result<WriteMeta> {
let mut path = format!("/v1/agent/join/{}", address);
if wan {
path.push_str("?wan=1");
}
let builder = self.client.put(&path);
self.client.write_empty(builder).await
}
pub async fn leave(&self) -> Result<WriteMeta> {
let builder = self.client.put("/v1/agent/leave");
self.client.write_empty(builder).await
}
}