diff --git a/Cargo.toml b/Cargo.toml index 6b85327..fc616df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "df-consul" description = "Deuxfleurs' async Rust bindings for (a subset of) the Consul HTTP API" authors = [ "Alex Auvolat " ] -version = "0.3.0" +version = "0.3.1" edition = "2021" license = "MIT" repository = "https://git.deuxfleurs.fr/Deuxfleurs/df-consul" diff --git a/examples/test.rs b/examples/test.rs index ba29583..b3e6455 100644 --- a/examples/test.rs +++ b/examples/test.rs @@ -2,7 +2,7 @@ use df_consul::*; #[tokio::main] async fn main() { - let config = ConsulConfig { + let config = Config { addr: "http://localhost:8500".into(), ca_cert: None, tls_skip_verify: false, diff --git a/src/catalog.rs b/src/catalog.rs index 952e99e..0f0ecf5 100644 --- a/src/catalog.rs +++ b/src/catalog.rs @@ -1,3 +1,8 @@ +//! Contains structures to interact with the catalog API +//! +//! See +//! for the full definition of the API. + use std::collections::HashMap; use std::fmt::Write; use std::sync::Arc; @@ -14,35 +19,44 @@ use tokio::sync::watch; use crate::{Consul, WithIndex}; +/// Node summary, as specified in response to "list nodes" API calls in +/// #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] -pub struct ConsulNode { +pub struct Node { pub node: String, pub address: String, pub meta: HashMap, } +/// One of the services returned in a CatalogNode #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] -pub struct ConsulService { +pub struct Service { pub service: String, pub address: String, pub port: u16, pub tags: Vec, } +/// Full node info, as specified in response to "retrieve map of services for a node" API call in +/// #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "PascalCase")] -pub struct ConsulCatalogNode { - pub node: ConsulNode, - pub services: HashMap, +pub struct CatalogNode { + pub node: Node, + pub services: HashMap, } -pub type ConsulServiceList = HashMap>; +/// Concise service list, as specified in response to "list services" API call in +/// +pub type ServiceList = HashMap>; +/// Node serving a service, as specified in response to "list nodes for a service" API call in +/// #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "PascalCase")] -pub struct ConsulServiceNode { +pub struct ServiceNode { pub node: String, pub address: String, pub node_meta: HashMap, @@ -52,17 +66,21 @@ pub struct ConsulServiceNode { pub service_port: u16, } +/// Node serving a service with health info, +/// as specified in response to "list service instances for a service" health API call in +/// #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] -pub struct ConsulHealthServiceNode { - pub node: ConsulNode, - pub service: ConsulService, - pub checks: Vec, +pub struct HealthServiceNode { + pub node: Node, + pub service: Service, + pub checks: Vec, } +/// A health check as returned in HealthServiceNode #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] -pub struct ConsulHealthCheck { +pub struct HealthCheck { pub node: String, #[serde(rename = "CheckID")] pub check_id: String, @@ -73,42 +91,56 @@ pub struct ConsulHealthCheck { pub type_: String, } -pub type AllServiceHealth = HashMap>; +/// Map containing all services and their associated nodes, with health checks, +/// returned by `watch_all_service_health` +pub type AllServiceHealth = HashMap>; impl Consul { + /// The "list nodes" API call of the Catalog API + /// + /// pub async fn catalog_node_list( &self, last_index: Option, - ) -> Result>> { + ) -> Result>> { self.get_with_index(format!("{}/v1/catalog/nodes", self.url), last_index) .await } + /// The "retrieve map of services for a node" API call of the Catalog API + /// + /// pub async fn catalog_node( &self, host: &str, last_index: Option, - ) -> Result>> { + ) -> Result>> { self.get_with_index(format!("{}/v1/catalog/node/{}", self.url, host), last_index) .await } + /// The "list services" API call of the Catalog api + /// + /// pub async fn catalog_service_list( &self, last_index: Option, - ) -> Result> { - self.get_with_index::( + ) -> Result> { + self.get_with_index::( format!("{}/v1/catalog/services", self.url), last_index, ) .await } + /// The "list nodes for a service" API call of the Catalog api + /// + /// pub async fn catalog_service_nodes( &self, service: &str, last_index: Option, - ) -> Result>> { + ) -> Result>> { self.get_with_index( format!("{}/v1/catalog/service/{}", self.url, service), last_index, @@ -116,11 +148,14 @@ impl Consul { .await } + /// The "list service instances for a service" API call of the Health api + /// + /// pub async fn health_service_instances( &self, service: &str, last_index: Option, - ) -> Result>> { + ) -> Result>> { self.get_with_index( format!("{}/v1/health/service/{}", self.url, service), last_index, @@ -128,6 +163,9 @@ impl Consul { .await } + /// Launches a background task that watches all services and the nodes that serve them, + /// and make that info available in a tokio watch channel. + /// The worker terminates when the channel is dropped. pub fn watch_all_service_health(&self) -> watch::Receiver { let (tx, rx) = watch::channel(HashMap::new()); diff --git a/src/lib.rs b/src/lib.rs index 6326fa0..c320936 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ -mod catalog; +pub mod catalog; +pub mod locking; mod kv; -mod locking; mod with_index; use std::fs::File; @@ -10,14 +10,23 @@ use anyhow::{bail, Result}; pub use with_index::WithIndex; -pub struct ConsulConfig { +/// Configuration parameters to talk to a Consul server +pub struct Config { + /// HTTP address of the Consul server, with `http://` or `https://` prefix pub addr: String, + /// CA certificate of the Consul CA, when using TLS pub ca_cert: Option, - pub tls_skip_verify: bool, + /// Client certificate for client auth when using TLS pub client_cert: Option, + /// Client key for client auth when using TLS pub client_key: Option, + /// Skip verification of consul server TLS certificates + pub tls_skip_verify: bool, } +/// Client used to talk to a Consul server. +/// All calls to the key/value API are automatically prefixed with an arbitrary string +/// that is constructed at client creation. #[derive(Clone)] pub struct Consul { client: reqwest::Client, @@ -27,7 +36,7 @@ pub struct Consul { } impl Consul { - pub fn new(config: ConsulConfig, kv_prefix: &str) -> Result { + pub fn new(config: Config, kv_prefix: &str) -> Result { let client = match (&config.client_cert, &config.client_key) { (Some(client_cert), Some(client_key)) => { let mut client_cert_buf = vec![]; diff --git a/src/locking.rs b/src/locking.rs index 12e9ac0..375d3f4 100644 --- a/src/locking.rs +++ b/src/locking.rs @@ -1,3 +1,8 @@ +//! Contains structures to interact with the locks/sessions API +//! +//! See +//! for the full definition of the API. + use anyhow::Result; use bytes::Bytes; use log::*; @@ -5,37 +10,33 @@ use serde::{Deserialize, Serialize}; use crate::Consul; +/// Session creation request as specified in +/// #[derive(Serialize, Deserialize, Debug)] -pub struct ConsulSessionRequest { - #[serde(rename = "Name")] +#[serde(rename_all = "PascalCase")] +pub struct SessionRequest { pub name: String, - - #[serde(rename = "Node")] pub node: Option, - - #[serde(rename = "LockDelay")] pub lock_delay: Option, - #[serde(rename = "TTL")] pub ttl: Option, - - #[serde(rename = "Behavior")] pub behavior: Option, } +/// (for internal use, mostly) #[derive(Serialize, Deserialize, Debug)] -pub struct ConsulSessionResponse { +pub struct SessionResponse { #[serde(rename = "ID")] pub id: String, } impl Consul { - pub async fn create_session(&self, req: &ConsulSessionRequest) -> Result { + pub async fn create_session(&self, req: &SessionRequest) -> Result { debug!("create_session {:?}", req); let url = format!("{}/v1/session/create", self.url); let http = self.client.put(&url).json(req).send().await?; - let resp: ConsulSessionResponse = http.json().await?; + let resp: SessionResponse = http.json().await?; Ok(resp.id) } diff --git a/src/with_index.rs b/src/with_index.rs index 90e06be..adce169 100644 --- a/src/with_index.rs +++ b/src/with_index.rs @@ -3,12 +3,16 @@ use std::fmt::{Debug, Display}; use anyhow::{bail, Result}; use reqwest::Response; +/// Wraps the returned value of an [API call with blocking +/// possibility](https://developer.hashicorp.com/consul/api-docs/features/blocking) with the +/// returned Consul index pub struct WithIndex { value: T, index: usize, } impl WithIndex { + /// (for internal use, mostly) pub fn index_from(resp: &Response) -> Result> { let index = match resp.headers().get("X-Consul-Index") { Some(v) => v.to_str()?.parse::()?, @@ -20,10 +24,13 @@ impl WithIndex { }) } + /// Returns the inner value, discarding the index pub fn into_inner(self) -> T { self.value } + /// Returns the Consul index, to be used in future calls to the same API endpoint to make them + /// blocking pub fn index(&self) -> usize { self.index } @@ -60,12 +67,14 @@ impl Display for WithIndex { } } +/// (for internal use, mostly) pub struct WithIndexBuilder { _phantom: std::marker::PhantomData, index: usize, } impl WithIndexBuilder { + /// (for internal use, mostly) pub fn value(self, value: T) -> WithIndex { WithIndex { value,