Documentate
This commit is contained in:
parent
db1d4411a9
commit
d0f40c02b9
6 changed files with 95 additions and 38 deletions
|
@ -2,7 +2,7 @@
|
||||||
name = "df-consul"
|
name = "df-consul"
|
||||||
description = "Deuxfleurs' async Rust bindings for (a subset of) the Consul HTTP API"
|
description = "Deuxfleurs' async Rust bindings for (a subset of) the Consul HTTP API"
|
||||||
authors = [ "Alex Auvolat <alex@adnab.me>" ]
|
authors = [ "Alex Auvolat <alex@adnab.me>" ]
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.deuxfleurs.fr/Deuxfleurs/df-consul"
|
repository = "https://git.deuxfleurs.fr/Deuxfleurs/df-consul"
|
||||||
|
|
|
@ -2,7 +2,7 @@ use df_consul::*;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let config = ConsulConfig {
|
let config = Config {
|
||||||
addr: "http://localhost:8500".into(),
|
addr: "http://localhost:8500".into(),
|
||||||
ca_cert: None,
|
ca_cert: None,
|
||||||
tls_skip_verify: false,
|
tls_skip_verify: false,
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
//! Contains structures to interact with the catalog API
|
||||||
|
//!
|
||||||
|
//! See <https://developer.hashicorp.com/consul/api-docs/catalog>
|
||||||
|
//! for the full definition of the API.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -14,35 +19,44 @@ use tokio::sync::watch;
|
||||||
|
|
||||||
use crate::{Consul, WithIndex};
|
use crate::{Consul, WithIndex};
|
||||||
|
|
||||||
|
/// Node summary, as specified in response to "list nodes" API calls in
|
||||||
|
/// <https://developer.hashicorp.com/consul/api-docs/catalog#list-nodes>
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct ConsulNode {
|
pub struct Node {
|
||||||
pub node: String,
|
pub node: String,
|
||||||
pub address: String,
|
pub address: String,
|
||||||
pub meta: HashMap<String, String>,
|
pub meta: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One of the services returned in a CatalogNode
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct ConsulService {
|
pub struct Service {
|
||||||
pub service: String,
|
pub service: String,
|
||||||
pub address: String,
|
pub address: String,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Full node info, as specified in response to "retrieve map of services for a node" API call in
|
||||||
|
/// <https://developer.hashicorp.com/consul/api-docs/catalog#retrieve-map-of-services-for-a-node>
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct ConsulCatalogNode {
|
pub struct CatalogNode {
|
||||||
pub node: ConsulNode,
|
pub node: Node,
|
||||||
pub services: HashMap<String, ConsulService>,
|
pub services: HashMap<String, Service>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ConsulServiceList = HashMap<String, Vec<String>>;
|
/// Concise service list, as specified in response to "list services" API call in
|
||||||
|
/// <https://developer.hashicorp.com/consul/api-docs/catalog#list-services>
|
||||||
|
pub type ServiceList = HashMap<String, Vec<String>>;
|
||||||
|
|
||||||
|
/// Node serving a service, as specified in response to "list nodes for a service" API call in
|
||||||
|
/// <https://developer.hashicorp.com/consul/api-docs/catalog#list-nodes-for-service>
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct ConsulServiceNode {
|
pub struct ServiceNode {
|
||||||
pub node: String,
|
pub node: String,
|
||||||
pub address: String,
|
pub address: String,
|
||||||
pub node_meta: HashMap<String, String>,
|
pub node_meta: HashMap<String, String>,
|
||||||
|
@ -52,17 +66,21 @@ pub struct ConsulServiceNode {
|
||||||
pub service_port: u16,
|
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
|
||||||
|
/// <https://developer.hashicorp.com/consul/api-docs/health#list-service-instances-for-service>
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct ConsulHealthServiceNode {
|
pub struct HealthServiceNode {
|
||||||
pub node: ConsulNode,
|
pub node: Node,
|
||||||
pub service: ConsulService,
|
pub service: Service,
|
||||||
pub checks: Vec<ConsulHealthCheck>,
|
pub checks: Vec<HealthCheck>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A health check as returned in HealthServiceNode
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(rename_all = "PascalCase")]
|
#[serde(rename_all = "PascalCase")]
|
||||||
pub struct ConsulHealthCheck {
|
pub struct HealthCheck {
|
||||||
pub node: String,
|
pub node: String,
|
||||||
#[serde(rename = "CheckID")]
|
#[serde(rename = "CheckID")]
|
||||||
pub check_id: String,
|
pub check_id: String,
|
||||||
|
@ -73,42 +91,56 @@ pub struct ConsulHealthCheck {
|
||||||
pub type_: String,
|
pub type_: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type AllServiceHealth = HashMap<String, Arc<[ConsulHealthServiceNode]>>;
|
/// Map containing all services and their associated nodes, with health checks,
|
||||||
|
/// returned by `watch_all_service_health`
|
||||||
|
pub type AllServiceHealth = HashMap<String, Arc<[HealthServiceNode]>>;
|
||||||
|
|
||||||
impl Consul {
|
impl Consul {
|
||||||
|
/// The "list nodes" API call of the Catalog API
|
||||||
|
///
|
||||||
|
/// <https://developer.hashicorp.com/consul/api-docs/catalog#list-nodes>
|
||||||
pub async fn catalog_node_list(
|
pub async fn catalog_node_list(
|
||||||
&self,
|
&self,
|
||||||
last_index: Option<usize>,
|
last_index: Option<usize>,
|
||||||
) -> Result<WithIndex<Vec<ConsulNode>>> {
|
) -> Result<WithIndex<Vec<Node>>> {
|
||||||
self.get_with_index(format!("{}/v1/catalog/nodes", self.url), last_index)
|
self.get_with_index(format!("{}/v1/catalog/nodes", self.url), last_index)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The "retrieve map of services for a node" API call of the Catalog API
|
||||||
|
///
|
||||||
|
/// <https://developer.hashicorp.com/consul/api-docs/catalog#retrieve-map-of-services-for-a-node>
|
||||||
pub async fn catalog_node(
|
pub async fn catalog_node(
|
||||||
&self,
|
&self,
|
||||||
host: &str,
|
host: &str,
|
||||||
last_index: Option<usize>,
|
last_index: Option<usize>,
|
||||||
) -> Result<WithIndex<Option<ConsulCatalogNode>>> {
|
) -> Result<WithIndex<Option<CatalogNode>>> {
|
||||||
self.get_with_index(format!("{}/v1/catalog/node/{}", self.url, host), last_index)
|
self.get_with_index(format!("{}/v1/catalog/node/{}", self.url, host), last_index)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The "list services" API call of the Catalog api
|
||||||
|
///
|
||||||
|
/// <https://developer.hashicorp.com/consul/api-docs/catalog#list-services>
|
||||||
pub async fn catalog_service_list(
|
pub async fn catalog_service_list(
|
||||||
&self,
|
&self,
|
||||||
last_index: Option<usize>,
|
last_index: Option<usize>,
|
||||||
) -> Result<WithIndex<ConsulServiceList>> {
|
) -> Result<WithIndex<ServiceList>> {
|
||||||
self.get_with_index::<ConsulServiceList>(
|
self.get_with_index::<ServiceList>(
|
||||||
format!("{}/v1/catalog/services", self.url),
|
format!("{}/v1/catalog/services", self.url),
|
||||||
last_index,
|
last_index,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The "list nodes for a service" API call of the Catalog api
|
||||||
|
///
|
||||||
|
/// <https://developer.hashicorp.com/consul/api-docs/catalog#list-nodes-for-service>
|
||||||
pub async fn catalog_service_nodes(
|
pub async fn catalog_service_nodes(
|
||||||
&self,
|
&self,
|
||||||
service: &str,
|
service: &str,
|
||||||
last_index: Option<usize>,
|
last_index: Option<usize>,
|
||||||
) -> Result<WithIndex<Vec<ConsulServiceNode>>> {
|
) -> Result<WithIndex<Vec<ServiceNode>>> {
|
||||||
self.get_with_index(
|
self.get_with_index(
|
||||||
format!("{}/v1/catalog/service/{}", self.url, service),
|
format!("{}/v1/catalog/service/{}", self.url, service),
|
||||||
last_index,
|
last_index,
|
||||||
|
@ -116,11 +148,14 @@ impl Consul {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The "list service instances for a service" API call of the Health api
|
||||||
|
///
|
||||||
|
/// <https://developer.hashicorp.com/consul/api-docs/health#list-service-instances-for-service>
|
||||||
pub async fn health_service_instances(
|
pub async fn health_service_instances(
|
||||||
&self,
|
&self,
|
||||||
service: &str,
|
service: &str,
|
||||||
last_index: Option<usize>,
|
last_index: Option<usize>,
|
||||||
) -> Result<WithIndex<Vec<ConsulHealthServiceNode>>> {
|
) -> Result<WithIndex<Vec<HealthServiceNode>>> {
|
||||||
self.get_with_index(
|
self.get_with_index(
|
||||||
format!("{}/v1/health/service/{}", self.url, service),
|
format!("{}/v1/health/service/{}", self.url, service),
|
||||||
last_index,
|
last_index,
|
||||||
|
@ -128,6 +163,9 @@ impl Consul {
|
||||||
.await
|
.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<AllServiceHealth> {
|
pub fn watch_all_service_health(&self) -> watch::Receiver<AllServiceHealth> {
|
||||||
let (tx, rx) = watch::channel(HashMap::new());
|
let (tx, rx) = watch::channel(HashMap::new());
|
||||||
|
|
||||||
|
|
19
src/lib.rs
19
src/lib.rs
|
@ -1,6 +1,6 @@
|
||||||
mod catalog;
|
pub mod catalog;
|
||||||
|
pub mod locking;
|
||||||
mod kv;
|
mod kv;
|
||||||
mod locking;
|
|
||||||
mod with_index;
|
mod with_index;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
@ -10,14 +10,23 @@ use anyhow::{bail, Result};
|
||||||
|
|
||||||
pub use with_index::WithIndex;
|
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,
|
pub addr: String,
|
||||||
|
/// CA certificate of the Consul CA, when using TLS
|
||||||
pub ca_cert: Option<String>,
|
pub ca_cert: Option<String>,
|
||||||
pub tls_skip_verify: bool,
|
/// Client certificate for client auth when using TLS
|
||||||
pub client_cert: Option<String>,
|
pub client_cert: Option<String>,
|
||||||
|
/// Client key for client auth when using TLS
|
||||||
pub client_key: Option<String>,
|
pub client_key: Option<String>,
|
||||||
|
/// 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)]
|
#[derive(Clone)]
|
||||||
pub struct Consul {
|
pub struct Consul {
|
||||||
client: reqwest::Client,
|
client: reqwest::Client,
|
||||||
|
@ -27,7 +36,7 @@ pub struct Consul {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Consul {
|
impl Consul {
|
||||||
pub fn new(config: ConsulConfig, kv_prefix: &str) -> Result<Self> {
|
pub fn new(config: Config, kv_prefix: &str) -> Result<Self> {
|
||||||
let client = match (&config.client_cert, &config.client_key) {
|
let client = match (&config.client_cert, &config.client_key) {
|
||||||
(Some(client_cert), Some(client_key)) => {
|
(Some(client_cert), Some(client_key)) => {
|
||||||
let mut client_cert_buf = vec![];
|
let mut client_cert_buf = vec![];
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
//! Contains structures to interact with the locks/sessions API
|
||||||
|
//!
|
||||||
|
//! See <https://developer.hashicorp.com/consul/api-docs/session>
|
||||||
|
//! for the full definition of the API.
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
@ -5,37 +10,33 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::Consul;
|
use crate::Consul;
|
||||||
|
|
||||||
|
/// Session creation request as specified in
|
||||||
|
/// <https://developer.hashicorp.com/consul/api-docs/session#create-session>
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct ConsulSessionRequest {
|
#[serde(rename_all = "PascalCase")]
|
||||||
#[serde(rename = "Name")]
|
pub struct SessionRequest {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
#[serde(rename = "Node")]
|
|
||||||
pub node: Option<String>,
|
pub node: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "LockDelay")]
|
|
||||||
pub lock_delay: Option<String>,
|
pub lock_delay: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "TTL")]
|
#[serde(rename = "TTL")]
|
||||||
pub ttl: Option<String>,
|
pub ttl: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "Behavior")]
|
|
||||||
pub behavior: Option<String>,
|
pub behavior: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// (for internal use, mostly)
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct ConsulSessionResponse {
|
pub struct SessionResponse {
|
||||||
#[serde(rename = "ID")]
|
#[serde(rename = "ID")]
|
||||||
pub id: String,
|
pub id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Consul {
|
impl Consul {
|
||||||
pub async fn create_session(&self, req: &ConsulSessionRequest) -> Result<String> {
|
pub async fn create_session(&self, req: &SessionRequest) -> Result<String> {
|
||||||
debug!("create_session {:?}", req);
|
debug!("create_session {:?}", req);
|
||||||
|
|
||||||
let url = format!("{}/v1/session/create", self.url);
|
let url = format!("{}/v1/session/create", self.url);
|
||||||
let http = self.client.put(&url).json(req).send().await?;
|
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)
|
Ok(resp.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,16 @@ use std::fmt::{Debug, Display};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use reqwest::Response;
|
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<T> {
|
pub struct WithIndex<T> {
|
||||||
value: T,
|
value: T,
|
||||||
index: usize,
|
index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> WithIndex<T> {
|
impl<T> WithIndex<T> {
|
||||||
|
/// (for internal use, mostly)
|
||||||
pub fn index_from(resp: &Response) -> Result<WithIndexBuilder<T>> {
|
pub fn index_from(resp: &Response) -> Result<WithIndexBuilder<T>> {
|
||||||
let index = match resp.headers().get("X-Consul-Index") {
|
let index = match resp.headers().get("X-Consul-Index") {
|
||||||
Some(v) => v.to_str()?.parse::<usize>()?,
|
Some(v) => v.to_str()?.parse::<usize>()?,
|
||||||
|
@ -20,10 +24,13 @@ impl<T> WithIndex<T> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the inner value, discarding the index
|
||||||
pub fn into_inner(self) -> T {
|
pub fn into_inner(self) -> T {
|
||||||
self.value
|
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 {
|
pub fn index(&self) -> usize {
|
||||||
self.index
|
self.index
|
||||||
}
|
}
|
||||||
|
@ -60,12 +67,14 @@ impl<T: Display> Display for WithIndex<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// (for internal use, mostly)
|
||||||
pub struct WithIndexBuilder<T> {
|
pub struct WithIndexBuilder<T> {
|
||||||
_phantom: std::marker::PhantomData<T>,
|
_phantom: std::marker::PhantomData<T>,
|
||||||
index: usize,
|
index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> WithIndexBuilder<T> {
|
impl<T> WithIndexBuilder<T> {
|
||||||
|
/// (for internal use, mostly)
|
||||||
pub fn value(self, value: T) -> WithIndex<T> {
|
pub fn value(self, value: T) -> WithIndex<T> {
|
||||||
WithIndex {
|
WithIndex {
|
||||||
value,
|
value,
|
||||||
|
|
Loading…
Reference in a new issue