consul_api/
lib.rs

1//#![warn(missing_docs)]
2
3use anyhow::{anyhow, Result};
4use reqwest::{
5    header::{HeaderMap, HeaderValue, AUTHORIZATION},
6    Method, Response, StatusCode,
7};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::time::Duration;
11
12// #[doc(hidden)]
13// pub use reqwest::header;
14#[doc(hidden)]
15pub use reqwest::Proxy;
16
17#[cfg(all(feature = "v1", feature = "v1_20_x"))]
18mod structs_1_20_x;
19#[cfg(all(feature = "v1", feature = "v1_20_x"))]
20pub use structs_1_20_x::*;
21
22#[derive(Clone)]
23pub struct Config {
24    pub token: String,
25    pub address: String,
26}
27
28impl Config {
29    pub fn from_env() -> Self {
30        Self {
31            token: read_env_or_default("CONSUL_TOKEN", ""),
32            address: read_env_or_default("CONSUL_ADDRESS", "http://127.0.0.1:8500"),
33        }
34    }
35}
36
37impl Default for Config {
38    fn default() -> Self {
39        Self::from_env()
40    }
41}
42
43pub struct ClientBuilder {
44    cfg: Config,
45    proxies: Vec<Proxy>,
46    timeout: Option<Duration>,
47}
48
49impl ClientBuilder {
50    pub fn new(cfg: Config) -> Self {
51        Self {
52            cfg,
53            proxies: vec![],
54            timeout: None,
55        }
56    }
57
58    pub fn with_proxy(mut self, proxy: Proxy) -> Self {
59        self.proxies.push(proxy);
60        self
61    }
62
63    pub fn with_timeout(mut self, timeout: Duration) -> Self {
64        self.timeout = Some(timeout);
65        self
66    }
67
68    pub fn build(self) -> Result<Client> {
69        let mut headers = HeaderMap::new();
70        if !self.cfg.token.is_empty() {
71            headers.insert(
72                AUTHORIZATION,
73                HeaderValue::from_str(&format!("Bearer {}", self.cfg.token)).unwrap(),
74            );
75        }
76
77        let mut builder = reqwest::ClientBuilder::new();
78        builder = builder.default_headers(headers);
79
80        for proxy in self.proxies {
81            // add proxy
82            builder = builder.proxy(proxy)
83        }
84
85        if let Some(v) = self.timeout {
86            builder = builder.timeout(v);
87        }
88
89        Ok(Client {
90            cfg: self.cfg,
91            http: builder.build()?,
92            #[cfg(feature = "v1")]
93            prefix: "/v1".to_string(),
94        })
95    }
96}
97
98#[derive(Debug, Default, Serialize, Deserialize)]
99pub struct FilterRequestQuery {
100    pub filter: Option<String>,
101
102    #[cfg(feature = "enterprise")]
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub ns: Option<String>,
105}
106
107#[derive(Debug, Default, Serialize, Deserialize)]
108pub struct DeregisterCheckRequestQuery {
109    #[serde(skip_serializing)]
110    pub check_id: String,
111
112    #[cfg(feature = "enterprise")]
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub ns: Option<String>,
115}
116
117#[derive(Debug, Default, Serialize, Deserialize)]
118pub struct AgentTTLCheckRequestQuery {
119    #[serde(skip_serializing)]
120    pub check_id: String,
121
122    pub note: Option<String>,
123
124    #[cfg(feature = "enterprise")]
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub ns: Option<String>,
127}
128
129#[derive(Debug, Default, Serialize, Deserialize)]
130pub struct AgentTTLCheckUpdateRequestQuery {
131    #[serde(skip_serializing)]
132    pub check_id: String,
133
134    #[cfg(feature = "enterprise")]
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub ns: Option<String>,
137}
138
139#[derive(Debug, Default, Serialize, Deserialize)]
140pub struct AgentTTLCheckUpdateRequestBody {
141    #[serde(rename = "Status")]
142    pub status: Option<String>,
143
144    #[serde(rename = "Output")]
145    pub output: Option<String>,
146}
147
148#[derive(Debug, Default, Serialize, Deserialize)]
149pub struct ServiceConfigurationRequestQuery {
150    #[serde(skip_serializing)]
151    pub service_id: String,
152
153    #[cfg(feature = "enterprise")]
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub ns: Option<String>,
156}
157
158#[derive(Debug, Default, Serialize, Deserialize)]
159pub struct LocalServiceHealthByNameRequestQuery {
160    #[cfg(feature = "enterprise")]
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub ns: Option<String>,
163}
164
165#[derive(Debug, Default, Serialize, Deserialize)]
166pub struct LocalServiceHealthByIDRequestQuery {
167    #[cfg(feature = "enterprise")]
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub ns: Option<String>,
170}
171
172#[derive(Debug, Default, Serialize, Deserialize)]
173pub struct RegisterServiceRequestQuery {
174    /// Missing health checks from the request will be deleted from the agent.
175    /// Using this parameter allows to idempotently register a service and
176    /// its checks without having to manually deregister checks.
177    #[serde(rename = "replace-existing-checks")]
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub replace_existing_checks: Option<String>,
180
181    #[cfg(feature = "enterprise")]
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub ns: Option<String>,
184}
185
186#[derive(Debug, Default, Serialize, Deserialize)]
187pub struct DeregisterServiceRequestQuery {
188    #[cfg(feature = "enterprise")]
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub ns: Option<String>,
191
192    #[cfg(feature = "enterprise")]
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub partition: Option<String>,
195}
196
197#[derive(Debug, Default, Serialize, Deserialize)]
198pub struct EnableMaintenanceModeRequestQuery {
199    #[serde(skip_serializing)]
200    pub service_id: String,
201
202    /// Specifies whether to enable or disable maintenance mode.
203    /// This is specified as part of the URL as a query string parameter.
204    pub enable: bool,
205
206    /// Specifies a text string explaining the reason for placing the node
207    /// into maintenance mode. This is simply to aid human operators. If no
208    /// reason is provided, a default value is used instead. This parameter
209    /// must be URI-encoded.
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub reason: Option<String>,
212
213    #[cfg(feature = "enterprise")]
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub ns: Option<String>,
216}
217
218#[derive(Debug, Default, Serialize, Deserialize)]
219pub struct ConnectAuthorizeRequestQuery {
220    #[cfg(feature = "enterprise")]
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub ns: Option<String>,
223}
224
225#[derive(Debug, Default, Serialize, Deserialize)]
226pub struct ConnectAuthorizeRequestReply {
227    /// True if authorized, false if not
228    #[serde(rename = "Authorized")]
229    pub authorized: bool,
230
231    /// Reason for the Authorized value (whether true or false)
232    #[serde(rename = "Reason")]
233    pub reason: String,
234}
235
236#[derive(Debug, Default, Serialize, Deserialize)]
237pub struct KVReadKeyQuery {
238    /// Specifies the datacenter to query. This will default to the
239    /// datacenter of the agent being queried.
240    pub dc: Option<String>,
241
242    /// Specifies if the lookup should be recursive and treat key as a
243    /// prefix instead of a literal match.
244    pub recurse: Option<bool>,
245
246    /// Specifies the response is just the raw value of the key, without
247    /// any encoding or metadata.
248    pub raw: Option<bool>,
249
250    /// Specifies to return only keys (no values or metadata). Specifying
251    /// this parameter implies recurse.
252    pub keys: Option<bool>,
253
254    /// Specifies the string to use as a separator for recursive key
255    /// lookups. This option is only used when paired with the keys
256    /// parameter to limit the prefix of keys returned, only up to the
257    /// given separator.
258    pub separator: Option<String>,
259
260    #[cfg(feature = "enterprise")]
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub ns: Option<String>,
263
264    /// The admin partition to use. If not provided, the partition is
265    /// inferred from the request's ACL token, or defaults to the default
266    /// partition.
267    #[cfg(feature = "enterprise")]
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub partition: Option<String>,
270}
271
272#[derive(Debug, Default, Serialize, Deserialize)]
273pub struct KVCreateOrUpdateKeyQuery {
274    /// Specifies the datacenter to query. This will default to the
275    /// datacenter of the agent being queried.
276    pub dc: Option<String>,
277
278    /// Specifies an unsigned value between 0 and (2^64)-1 to store with
279    /// the key. API consumers can use this field any way they choose for
280    /// their application.
281    pub flags: Option<u64>,
282
283    /// Specifies to use a Check-And-Set operation. This is very useful as a
284    /// building block for more complex synchronization primitives. If the
285    /// index is 0, Consul will only put the key if it does not already exist.
286    /// If the index is non-zero, the key is only set if the index matches the
287    /// ModifyIndex of that key.
288    pub cas: Option<u64>,
289
290    /// Supply a session ID to use in a lock acquisition operation. This is
291    /// useful as it allows leader election to be built on top of Consul. If
292    /// the lock is not held and the session is valid, this increments the
293    /// LockIndex and sets the Session value of the key in addition to updating
294    /// the key contents. A key does not need to exist to be acquired. If the
295    /// lock is already held by the given session, then the LockIndex is not
296    /// incremented but the key contents are updated. This lets the current
297    /// lock holder update the key contents without having to give up the lock
298    /// and reacquire it. Note that an update that does not include the acquire
299    /// parameter will proceed normally even if another session has locked the
300    /// key.
301    pub acquire: Option<String>,
302
303    /// Supply a session ID to use in a release operation. This is useful when
304    /// paired with ?acquire= as it allows clients to yield a lock. This will
305    /// leave the LockIndex unmodified but will clear the associated Session of
306    /// the key. The key must be held by this session to be unlocked.
307    pub release: Option<String>,
308
309    #[cfg(feature = "enterprise")]
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub ns: Option<String>,
312
313    #[cfg(feature = "enterprise")]
314    #[serde(skip_serializing_if = "Option::is_none")]
315    pub partition: Option<String>,
316}
317
318#[derive(Debug, Default, Serialize, Deserialize)]
319pub struct KVDeleteKeyQuery {
320    /// Specifies the datacenter to query. This will default to the datacenter
321    /// of the agent being queried. If the DC is invalid, the error "No path to
322    /// datacenter" is returned.
323    pub dc: Option<String>,
324
325    /// Specifies to delete all keys which have the specified prefix. Without
326    /// this, only a key with an exact match will be deleted.
327    pub recurse: Option<bool>,
328
329    /// Specifies to use a Check-And-Set operation. This is very useful as a
330    /// building block for more complex synchronization primitives. Unlike PUT,
331    /// the index must be greater than 0 for Consul to take any action: a 0
332    /// index will not delete the key. If the index is non-zero, the key is
333    /// only deleted if the index matches the ModifyIndex of that key.
334    pub cas: Option<u64>,
335
336    #[cfg(feature = "enterprise")]
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub ns: Option<String>,
339
340    #[cfg(feature = "enterprise")]
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub partition: Option<String>,
343}
344
345#[derive(Debug, Default, Serialize, Deserialize)]
346pub struct CatalogRegisterEntityQuery {
347    #[cfg(feature = "enterprise")]
348    #[serde(skip_serializing_if = "Option::is_none")]
349    pub ns: Option<String>,
350}
351
352#[derive(Debug, Default, Serialize, Deserialize)]
353pub struct CatalogDeregisterEntityQuery {
354    #[cfg(feature = "enterprise")]
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub ns: Option<String>,
357}
358
359#[derive(Debug, Default, Serialize, Deserialize)]
360pub struct CatalogListServicesQuery {
361    #[serde(skip_serializing_if = "Option::is_none")]
362    pub dc: Option<String>,
363
364    #[serde(rename = "node-meta")]
365    #[serde(skip_serializing_if = "Option::is_none")]
366    pub node_meta: Option<String>,
367
368    #[serde(skip_serializing_if = "Option::is_none")]
369    pub filter: Option<String>,
370
371    #[cfg(feature = "enterprise")]
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub ns: Option<String>,
374
375    #[cfg(feature = "enterprise")]
376    #[serde(skip_serializing_if = "Option::is_none")]
377    pub partition: Option<String>,
378}
379
380#[derive(Debug, Default, Serialize, Deserialize)]
381pub struct CatalogListNodesForServiceQuery {
382    #[serde(skip_serializing_if = "Option::is_none")]
383    pub dc: Option<String>,
384
385    #[serde(skip_serializing_if = "Option::is_none")]
386    pub tag: Option<String>,
387
388    #[serde(skip_serializing_if = "Option::is_none")]
389    pub near: Option<String>,
390
391    #[serde(rename = "node-meta")]
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub node_meta: Option<String>,
394
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub filter: Option<String>,
397
398    #[cfg(feature = "enterprise")]
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub ns: Option<String>,
401}
402
403#[derive(Debug, Default, Serialize, Deserialize)]
404pub struct CatalogNodeServicesQuery {
405    #[serde(skip_serializing_if = "Option::is_none")]
406    pub dc: Option<String>,
407
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub filter: Option<String>,
410
411    #[cfg(feature = "enterprise")]
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub ns: Option<String>,
414}
415
416#[derive(Debug, Default, Serialize, Deserialize)]
417pub struct CatalogGatewayServicesQuery {
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub dc: Option<String>,
420
421    #[cfg(feature = "enterprise")]
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub ns: Option<String>,
424}
425
426#[derive(Debug, Default, Serialize, Deserialize)]
427pub struct EventFireQuery {
428    #[serde(skip_serializing_if = "Option::is_none")]
429    pub dc: Option<String>,
430
431    #[serde(skip_serializing_if = "Option::is_none")]
432    pub node: Option<String>,
433
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub service: Option<String>,
436
437    #[serde(skip_serializing_if = "Option::is_none")]
438    pub tag: Option<String>,
439}
440
441#[derive(Debug, Default, Serialize, Deserialize)]
442pub struct EventListQuery {
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub name: Option<String>,
445
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub node: Option<String>,
448
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub service: Option<String>,
451
452    #[serde(skip_serializing_if = "Option::is_none")]
453    pub tag: Option<String>,
454}
455
456#[derive(Debug, Default, Serialize, Deserialize)]
457pub struct HealthListNodesQuery {
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub dc: Option<String>,
460
461    #[serde(skip_serializing_if = "Option::is_none")]
462    pub filter: Option<String>,
463
464    #[cfg(feature = "enterprise")]
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub ns: Option<String>,
467}
468
469#[derive(Debug, Default, Serialize, Deserialize)]
470pub struct HealthListServicesQuery {
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub dc: Option<String>,
473
474    #[serde(skip_serializing_if = "Option::is_none")]
475    pub near: Option<String>,
476
477    #[serde(rename = "node-meta")]
478    #[serde(skip_serializing_if = "Option::is_none")]
479    pub node_meta: Option<String>,
480
481    #[serde(skip_serializing_if = "Option::is_none")]
482    pub filter: Option<String>,
483
484    #[cfg(feature = "enterprise")]
485    #[serde(skip_serializing_if = "Option::is_none")]
486    pub ns: Option<String>,
487}
488
489#[derive(Debug, Default, Serialize, Deserialize)]
490pub struct HealthListServiceInstancesQuery {
491    #[serde(skip_serializing_if = "Option::is_none")]
492    pub dc: Option<String>,
493
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub near: Option<String>,
496
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub tag: Option<String>,
499
500    #[serde(rename = "node-meta")]
501    #[serde(skip_serializing_if = "Option::is_none")]
502    pub node_meta: Option<String>,
503
504    #[serde(skip_serializing_if = "Option::is_none")]
505    pub passing: Option<bool>,
506
507    #[serde(skip_serializing_if = "Option::is_none")]
508    pub filter: Option<String>,
509
510    #[serde(skip_serializing_if = "Option::is_none")]
511    pub peer: Option<String>,
512
513    #[serde(skip_serializing_if = "Option::is_none")]
514    pub index: Option<u64>,
515
516    #[cfg(feature = "enterprise")]
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub ns: Option<String>,
519
520    #[cfg(feature = "enterprise")]
521    #[serde(skip_serializing_if = "Option::is_none")]
522    pub sg: Option<String>,
523}
524
525#[derive(Debug, Default, Serialize, Deserialize)]
526pub struct HealthListStateQuery {
527    #[serde(skip_serializing_if = "Option::is_none")]
528    pub dc: Option<String>,
529
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub near: Option<String>,
532
533    #[serde(rename = "node-meta")]
534    #[serde(skip_serializing_if = "Option::is_none")]
535    pub node_meta: Option<String>,
536
537    #[serde(skip_serializing_if = "Option::is_none")]
538    pub filter: Option<String>,
539
540    #[cfg(feature = "enterprise")]
541    #[serde(skip_serializing_if = "Option::is_none")]
542    pub ns: Option<String>,
543}
544
545#[cfg(feature = "enterprise")]
546#[derive(Debug, Default, Serialize, Deserialize)]
547pub struct NamespaceCreateBody {
548    /// The namespace's name. This field must be a valid DNS hostname label.
549    ///
550    /// required
551    ///
552    #[serde(rename = "Name")]
553    pub name: String,
554
555    /// Free form namespaces description.
556    #[serde(rename = "Description")]
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub description: Option<String>,
559
560    #[serde(rename = "ACLs")]
561    #[serde(skip_serializing_if = "Option::is_none")]
562    pub acls: Option<NamespaceACLConfig>,
563
564    #[serde(rename = "Meta")]
565    #[serde(skip_serializing_if = "Option::is_none")]
566    pub meta: Option<::std::collections::HashMap<String, String>>,
567
568    #[serde(rename = "Partition")]
569    #[serde(skip_serializing_if = "Option::is_none")]
570    pub partition: Option<String>,
571}
572
573#[cfg(feature = "enterprise")]
574#[derive(Debug, Default, Serialize, Deserialize)]
575pub struct NamespaceDetail {
576    /// The namespace's name.
577    #[serde(rename = "Name")]
578    pub name: String,
579
580    /// Free form namespaces description.
581    #[serde(rename = "Description")]
582    #[serde(skip_serializing_if = "Option::is_none")]
583    pub description: Option<String>,
584
585    #[serde(rename = "ACLs")]
586    #[serde(skip_serializing_if = "Option::is_none")]
587    pub acls: Option<NamespaceACLConfig>,
588
589    #[serde(rename = "Meta")]
590    #[serde(skip_serializing_if = "Option::is_none")]
591    pub meta: Option<::std::collections::HashMap<String, String>>,
592
593    #[serde(rename = "CreateIndex")]
594    pub create_index: u64,
595
596    #[serde(rename = "ModifyIndex")]
597    pub modify_index: u64,
598}
599
600#[cfg(feature = "enterprise")]
601#[derive(Debug, Default, Serialize, Deserialize)]
602pub struct NamespaceReadQuery {
603    #[serde(skip_serializing_if = "Option::is_none")]
604    pub partition: Option<String>,
605}
606
607#[cfg(feature = "enterprise")]
608#[derive(Debug, Default, Serialize, Deserialize)]
609pub struct NamespaceUpdateBody {
610    /// If specified, this field must be an exact match with the name path
611    /// parameter.
612    #[serde(rename = "Name")]
613    #[serde(skip_serializing_if = "Option::is_none")]
614    pub name: Option<String>,
615
616    /// Free form namespaces description.
617    #[serde(rename = "Description")]
618    #[serde(skip_serializing_if = "Option::is_none")]
619    pub description: Option<String>,
620
621    #[serde(rename = "ACLs")]
622    #[serde(skip_serializing_if = "Option::is_none")]
623    pub acls: Option<NamespaceACLConfig>,
624
625    #[serde(rename = "Meta")]
626    #[serde(skip_serializing_if = "Option::is_none")]
627    pub meta: Option<::std::collections::HashMap<String, String>>,
628
629    #[serde(rename = "Partition")]
630    #[serde(skip_serializing_if = "Option::is_none")]
631    pub partition: Option<String>,
632}
633
634#[derive(Debug, Default, Serialize, Deserialize)]
635pub struct StatusQuery {
636    #[serde(skip_serializing_if = "Option::is_none")]
637    pub dc: Option<String>,
638}
639
640/// The Consul API client.
641#[derive(Clone)]
642pub struct Client {
643    cfg: Config,
644    http: reqwest::Client,
645    prefix: String,
646}
647
648impl Client {
649    /// Creates a new client with the default configuration.
650    pub fn new() -> Self {
651        ClientBuilder::new(Config::default()).build().unwrap()
652    }
653
654    /// List Checks
655    /// This endpoint returns all checks that are registered with the local agent.
656    /// These checks were either provided through configuration files or added
657    /// dynamically using the HTTP API.
658    pub async fn agent_checks(
659        &self,
660        q: &FilterRequestQuery,
661    ) -> Result<HashMap<String, HealthCheck>> {
662        let resp = self
663            .execute_request(Method::GET, "/agent/checks", q, None, &())
664            .await?;
665
666        resp.json().await.map_err(|e| anyhow!(e))
667    }
668
669    /// Register Check
670    /// This endpoint adds a new check to the local agent. Checks may be of script,
671    /// HTTP, TCP, UDP, or TTL type. The agent is responsible for managing the
672    /// status of the check and keeping the Catalog in sync.
673    pub async fn agent_check_register(&self, b: &CheckDefinition) -> Result<bool> {
674        let resp = self
675            .execute_request(Method::PUT, "/agent/check/register", &(), None, b)
676            .await?;
677        Ok(resp.status() == StatusCode::OK)
678    }
679
680    /// Deregister Check
681    /// This endpoint remove a check from the local agent. The agent will take care of
682    /// deregistering the check from the catalog. If the check with the provided ID
683    /// does not exist, no action is taken.
684    pub async fn agent_check_deregister(&self, q: &DeregisterCheckRequestQuery) -> Result<bool> {
685        let path = format!("/agent/check/deregister/{}", q.check_id);
686        let resp = self
687            .execute_request(Method::PUT, &path, q, None, &())
688            .await?;
689        Ok(resp.status() == StatusCode::OK)
690    }
691
692    /// TTL Check Pass
693    /// This endpoint is used with a TTL type check to set the status of the check
694    /// to passing and to reset the TTL clock.
695    pub async fn agent_check_pass(&self, q: &AgentTTLCheckRequestQuery) -> Result<bool> {
696        let path = format!("/agent/check/pass/{}", q.check_id);
697        let resp = self
698            .execute_request(Method::PUT, &path, q, None, &())
699            .await?;
700
701        Ok(resp.status() == StatusCode::OK)
702    }
703
704    /// TTL Check Warn
705    /// This endpoint is used with a TTL type check to set the status of the check
706    /// to warning and to reset the TTL clock.
707    pub async fn agent_check_warn(&self, q: &AgentTTLCheckRequestQuery) -> Result<()> {
708        let path = format!("/agent/check/warn/{}", q.check_id);
709        let resp = self
710            .execute_request(Method::PUT, &path, q, None, &())
711            .await?;
712        resp.json().await.map_err(|e| anyhow!(e))
713    }
714
715    /// TTL Check Fail
716    /// This endpoint is used with a TTL type check to set the status of the check
717    /// to critical and to reset the TTL clock.
718    pub async fn agent_check_fail(&self, q: &AgentTTLCheckRequestQuery) -> Result<bool> {
719        let path = format!("/agent/check/fail/{}", q.check_id);
720        let resp = self
721            .execute_request(Method::PUT, &path, q, None, &())
722            .await?;
723        Ok(resp.status() == StatusCode::OK)
724    }
725
726    /// TTL Check Update
727    /// This endpoint is used with a TTL type check to set the status of the check
728    /// and to reset the TTL clock.
729    pub async fn agent_check_update(
730        &self,
731        q: &AgentTTLCheckUpdateRequestQuery,
732        b: &AgentTTLCheckUpdateRequestBody,
733    ) -> Result<bool> {
734        let path = format!("/agent/check/update/{}", q.check_id);
735        let resp = self.execute_request(Method::PUT, &path, q, None, b).await?;
736        Ok(resp.status() == StatusCode::OK)
737    }
738
739    /// List Services
740    /// This endpoint returns all the services that are registered with the local agent.
741    /// These services were either provided through configuration files or added
742    /// dynamically using the HTTP API.
743    pub async fn agent_services(
744        &self,
745        q: &FilterRequestQuery,
746    ) -> Result<HashMap<String, AgentService>> {
747        let resp = self
748            .execute_request(Method::GET, "/agent/services", q, None, &())
749            .await?;
750        resp.json().await.map_err(|e| anyhow!(e))
751    }
752
753    pub async fn agent_service_configuration(
754        &self,
755        q: &ServiceConfigurationRequestQuery,
756    ) -> Result<Option<AgentService>> {
757        let path = format!("/agent/service/{}", q.service_id);
758        let resp = self
759            .execute_request(Method::GET, &path, q, None, &())
760            .await?;
761
762        if resp.status() == StatusCode::NOT_FOUND {
763            return Ok(None);
764        }
765
766        Ok(Some(resp.json().await.map_err(|e| anyhow!(e))?))
767    }
768
769    /// Get local service health
770    /// Retrieve an aggregated state of service(s) on the local agent by name.
771    ///
772    /// This endpoints support JSON format and text/plain formats, JSON
773    /// being the default. In order to get the text format, you can
774    /// append ?format=text to the URL or use Mime Content negotiation
775    /// by specifying a HTTP Header Accept starting with text/plain.
776    pub async fn agent_get_service_health_by_name<S: Into<String>>(
777        &self,
778        service_name: S,
779        q: &LocalServiceHealthByNameRequestQuery,
780    ) -> Result<Vec<AgentServiceChecksInfo>> {
781        let path = format!("/agent/health/service/name/{}", service_name.into());
782        let resp = self
783            .execute_request(Method::GET, &path, q, None, &())
784            .await?;
785        resp.json().await.map_err(|e| anyhow!(e))
786    }
787
788    /// Get local service health by ID
789    /// Retrieve the health state of a specific service on the local agent
790    /// by ID.
791    pub async fn agent_get_service_health_by_id<S: Into<String>>(
792        &self,
793        service_id: S,
794        q: &LocalServiceHealthByIDRequestQuery,
795    ) -> Result<Option<AgentServiceChecksInfo>> {
796        let path = format!("/agent/health/service/id/{}", service_id.into());
797        let resp = self
798            .execute_request(Method::GET, &path, q, None, &())
799            .await?;
800
801        if resp.status() == StatusCode::NOT_FOUND {
802            return Ok(None);
803        }
804
805        Ok(Some(resp.json().await.map_err(|e| anyhow!(e))?))
806    }
807
808    /// Register Service
809    /// This endpoint adds a new service, with optional health checks, to the
810    /// local agent.
811    ///
812    /// The agent is responsible for managing the status of its local services, and
813    /// for sending updates about its local services to the servers to keep the
814    /// global catalog in sync.
815    pub async fn agent_register_service(
816        &self,
817        q: &RegisterServiceRequestQuery,
818        b: &ServiceDefinition,
819    ) -> Result<bool> {
820        let resp = self
821            .execute_request(Method::PUT, "/agent/service/register", q, None, b)
822            .await?;
823        Ok(resp.status() == StatusCode::OK)
824    }
825
826    /// Deregister Service
827    /// This endpoint removes a service from the local agent. If the service
828    /// does not exist, no action is taken.
829    ///
830    /// The agent will take care of deregistering the service with the catalog.
831    /// If there is an associated check, that is also deregistered.
832    pub async fn agent_deregister_service<S: Into<String>>(
833        &self,
834        service_id: S,
835        q: &DeregisterServiceRequestQuery,
836    ) -> Result<bool> {
837        let path = format!("/agent/service/deregister/{}", service_id.into());
838        let resp = self
839            .execute_request(Method::PUT, &path, q, None, &())
840            .await?;
841        Ok(resp.status() == StatusCode::OK)
842    }
843
844    /// Enable Maintenance Mode
845    ///
846    /// This endpoint places a given service into "maintenance mode". During
847    /// maintenance mode, the service will be marked as unavailable and will
848    /// not be present in DNS or API queries. This API call is idempotent.
849    /// Maintenance mode is persistent and will be automatically restored on
850    /// agent restart.
851    pub async fn agent_enable_maintenance_mode(
852        &self,
853        q: &EnableMaintenanceModeRequestQuery,
854    ) -> Result<bool> {
855        let path = format!("/agent/service/maintenance/{}", q.service_id);
856        let resp = self
857            .execute_request(Method::PUT, &path, q, None, &())
858            .await?;
859        Ok(resp.status() == StatusCode::OK)
860    }
861
862    pub async fn agent_connect_authorize(
863        &self,
864        q: &ConnectAuthorizeRequestQuery,
865        b: &ConnectAuthorizeRequest,
866    ) -> Result<ConnectAuthorizeRequestReply> {
867        let resp = self
868            .execute_request(Method::POST, "/agent/connect/authorize", q, None, b)
869            .await?;
870        resp.json().await.map_err(|e| anyhow!(e))
871    }
872
873    /// Catalog Register Entity
874    /// This endpoint is a low-level mechanism for registering or updating
875    /// entries in the catalog. It is usually preferable to instead use the
876    /// agent endpoints for registration as they are simpler and perform
877    /// anti-entropy.
878    pub async fn catalog_register_entity(
879        &self,
880        q: &CatalogRegisterEntityQuery,
881        b: &RegisterRequest,
882    ) -> Result<bool> {
883        let resp = self
884            .execute_request(Method::PUT, "/catalog/register", q, None, b)
885            .await?;
886
887        Ok(resp.status() == StatusCode::OK)
888    }
889
890    /// Catalog Deregister Entity
891    /// This endpoint is a low-level mechanism for directly removing entries
892    /// from the Catalog. It is usually preferable to instead use the agent
893    /// endpoints for deregistration as they are simpler and perform
894    /// anti-entropy.
895    pub async fn catalog_deregister_entity(
896        &self,
897        q: &CatalogDeregisterEntityQuery,
898        b: &DeregisterRequest,
899    ) -> Result<bool> {
900        let resp = self
901            .execute_request(Method::PUT, "/catalog/deregister", q, None, b)
902            .await?;
903
904        Ok(resp.status() == StatusCode::OK)
905    }
906
907    /// Catalog List Datacenters
908    /// This endpoint returns the list of all known datacenters. The
909    /// datacenters will be sorted in ascending order based on the estimated
910    /// median round trip time from the server to the servers in that
911    /// datacenter.
912    pub async fn catalog_list_datacenters(&self) -> Result<Vec<String>> {
913        let resp = self
914            .execute_request(Method::GET, "/catalog/datacenters", &(), None, &())
915            .await?;
916
917        resp.json().await.map_err(|e| anyhow!(e))
918    }
919
920    /// Catalog List Nodes
921    /// This endpoint and returns the nodes registered in a given datacenter.
922    pub async fn catalog_list_nodes(&self) -> Result<Vec<Node>> {
923        let resp = self
924            .execute_request(Method::GET, "/catalog/nodes", &(), None, &())
925            .await?;
926
927        resp.json().await.map_err(|e| anyhow!(e))
928    }
929
930    /// Catalog List Services
931    /// This endpoint returns the services registered in a given datacenter.
932    pub async fn catalog_list_services(
933        &self,
934        q: &CatalogListServicesQuery,
935    ) -> Result<::std::collections::HashMap<String, Vec<String>>> {
936        let resp = self
937            .execute_request(Method::GET, "/catalog/services", q, None, &())
938            .await?;
939
940        resp.json().await.map_err(|e| anyhow!(e))
941    }
942
943    /// Catalog List Nodes for Service
944    /// This endpoint returns the nodes providing a service in a given
945    /// datacenter.
946    pub async fn catalog_list_nodes_for_service<S: Into<String>>(
947        &self,
948        service_name: S,
949        q: &CatalogListNodesForServiceQuery,
950    ) -> Result<Vec<ServiceNode>> {
951        let p = format!("/catalog/service/{}", service_name.into());
952
953        let resp = self.execute_request(Method::GET, &p, q, None, &()).await?;
954
955        resp.json().await.map_err(|e| anyhow!(e))
956    }
957
958    /// List Nodes for Mesh-capable Service
959    /// This endpoint returns the nodes providing a mesh-capable service in a
960    /// given datacenter. This will include both proxies and native
961    /// integrations. A service may register both mesh-capable and incapable
962    /// services at the same time, so this endpoint may be used to filter only
963    /// the mesh-capable endpoints.
964    pub async fn catalog_list_nodes_for_mesh_capable_service<S: Into<String>>(
965        &self,
966        service: S,
967        q: &CatalogListNodesForServiceQuery,
968    ) -> Result<Vec<ServiceNode>> {
969        let p = format!("/catalog/connect/{}", service.into());
970
971        let resp = self.execute_request(Method::GET, &p, q, None, &()).await?;
972
973        resp.json().await.map_err(|e| anyhow!(e))
974    }
975
976    /// Retrieve Map of Services for a Node
977    /// This endpoint returns the node's registered services.
978    pub async fn catalog_node_services<S: Into<String>>(
979        &self,
980        node_name: S,
981        q: &CatalogNodeServicesQuery,
982    ) -> Result<Option<NodeServices>> {
983        let p = format!("/catalog/node/{}", node_name.into());
984
985        let resp = self.execute_request(Method::GET, &p, q, None, &()).await?;
986
987        if resp.status() == StatusCode::NOT_FOUND {
988            return Ok(None);
989        }
990
991        resp.json().await.map_err(|e| anyhow!(e))
992    }
993
994    /// List Services for Gateway
995    /// This endpoint returns the services associated with an ingress gateway
996    /// or terminating gateway.
997    pub async fn catalog_gateway_services<S: Into<String>>(
998        &self,
999        gateway: S,
1000        q: &CatalogGatewayServicesQuery,
1001    ) -> Result<Vec<GatewayService>> {
1002        let p = format!("/catalog/gateway-services/{}", gateway.into());
1003
1004        let resp = self.execute_request(Method::GET, &p, q, None, &()).await?;
1005
1006        resp.json().await.map_err(|e| anyhow!(e))
1007    }
1008
1009    /// Fire Event
1010    /// This endpoint triggers a new user event.
1011    pub async fn event_fire<S: Into<String>>(
1012        &self,
1013        name: S,
1014        body: Option<Vec<u8>>,
1015        q: &EventFireQuery,
1016    ) -> Result<bool> {
1017        let p = format!("/event/fire/{}", name.into());
1018
1019        let resp = self.execute_request(Method::PUT, &p, q, body, &()).await?;
1020
1021        Ok(resp.status() == StatusCode::OK)
1022    }
1023
1024    /// List Events
1025    /// This endpoint returns the most recent events (up to 256) known by the
1026    /// agent. As a consequence of how the event command works, each agent may
1027    /// have a different view of the events. Events are broadcast using the
1028    /// gossip protocol, so they have no global ordering nor do they make a
1029    /// promise of delivery.
1030    pub async fn event_list(&self, q: &EventListQuery) -> Result<Vec<UserEvent>> {
1031        let resp = self
1032            .execute_request(Method::GET, "/event/list", q, None, &())
1033            .await?;
1034
1035        let mut list: Vec<UserEvent> = resp.json().await.map_err(|e| anyhow!(e))?;
1036
1037        for item in list.iter_mut() {
1038            item.payload = item.payload.clone().map_or(None, |v| {
1039                // 'bnVsbA==' is null
1040                if v.0 == "bnVsbA==" {
1041                    None
1042                } else {
1043                    Some(v)
1044                }
1045            })
1046        }
1047
1048        Ok(list)
1049    }
1050
1051    /// List Checks for Node
1052    /// This endpoint returns the checks specific to the node provided on the
1053    /// path.
1054    pub async fn health_list_nodes<S: Into<String>>(
1055        &self,
1056        node: S,
1057        q: &HealthListNodesQuery,
1058    ) -> Result<Vec<HealthCheck>> {
1059        let p = format!("/health/node/{}", node.into());
1060
1061        let resp = self.execute_request(Method::GET, &p, q, None, &()).await?;
1062
1063        resp.json().await.map_err(|e| anyhow!(e))
1064    }
1065
1066    /// List Checks for Service
1067    /// This endpoint returns the checks associated with the service provided
1068    /// on the path.
1069    pub async fn health_list_services<S: Into<String>>(
1070        &self,
1071        service: S,
1072        q: &HealthListServicesQuery,
1073    ) -> Result<Vec<HealthCheck>> {
1074        let p = format!("/health/checks/{}", service.into());
1075
1076        let resp = self.execute_request(Method::GET, &p, q, None, &()).await?;
1077
1078        resp.json().await.map_err(|e| anyhow!(e))
1079    }
1080
1081    /// List Service Instances for Service
1082    /// This endpoint returns the service instances providing the service
1083    /// indicated on the path. Users can also build in support for dynamic load
1084    /// balancing and other features by incorporating the use of health checks.
1085    pub async fn health_list_service_instances<S: Into<String>>(
1086        &self,
1087        service: S,
1088        q: &HealthListServiceInstancesQuery,
1089    ) -> Result<Vec<CheckServiceNode>> {
1090        let p = format!("/health/service/{}", service.into());
1091
1092        let resp = self.execute_request(Method::GET, &p, q, None, &()).await?;
1093
1094        resp.json().await.map_err(|e| anyhow!(e))
1095    }
1096
1097    /// List Service Instances for Mesh-enabled Service
1098    ///
1099    /// This endpoint returns the service instances providing a mesh-capable
1100    /// service in a given datacenter. This will include both proxies and
1101    /// native integrations. A service may register both mesh-capable and
1102    /// incapable services at the same time, so this endpoint may be used to
1103    /// filter only the mesh-capable endpoints.
1104    ///
1105    pub async fn health_list_service_instances_for_mesh_capable<S: Into<String>>(
1106        &self,
1107        service: S,
1108        q: &HealthListServiceInstancesQuery,
1109    ) -> Result<Vec<CheckServiceNode>> {
1110        let p = format!("/health/connect/{}", service.into());
1111
1112        let resp = self.execute_request(Method::GET, &p, q, None, &()).await?;
1113
1114        resp.json().await.map_err(|e| anyhow!(e))
1115    }
1116
1117    /// List Service Instances for Ingress Gateways Associated with a Service
1118    ///
1119    /// This API is available in Consul versions 1.8.0 and later.
1120    ///
1121    /// This endpoint returns the service instances providing an ingress
1122    /// gateway for a service in a given datacenter.
1123    ///
1124    pub async fn health_list_service_instances_for_ingress_gateways<S: Into<String>>(
1125        &self,
1126        service: S,
1127        q: &HealthListServiceInstancesQuery,
1128    ) -> Result<Vec<CheckServiceNode>> {
1129        let p = format!("/health/ingress/{}", service.into());
1130
1131        let resp = self.execute_request(Method::GET, &p, q, None, &()).await?;
1132
1133        resp.json().await.map_err(|e| anyhow!(e))
1134    }
1135
1136    /// List Checks in State
1137    ///
1138    /// This endpoint returns the checks in the state provided on the path.
1139    ///
1140    pub async fn health_list_state(
1141        &self,
1142        state: Health,
1143        q: &HealthListStateQuery,
1144    ) -> Result<Vec<HealthCheck>> {
1145        let p = format!("/health/state/{}", state);
1146
1147        let resp = self.execute_request(Method::GET, &p, q, None, &()).await?;
1148
1149        resp.json().await.map_err(|e| anyhow!(e))
1150    }
1151
1152    /// Read Key
1153    /// This endpoint returns the specified key. If no key exists at the given
1154    /// path, a 404 is returned instead of a 200 response.
1155    pub async fn kv_read_key<S: Into<String>>(
1156        &self,
1157        key: S,
1158        q: &KVReadKeyQuery,
1159    ) -> Result<Option<Vec<u8>>> {
1160        let path = format!("/kv/{}", key.into());
1161        let resp = self
1162            .execute_request(Method::GET, &path, q, None, &())
1163            .await?;
1164
1165        if resp.status() == StatusCode::NOT_FOUND {
1166            return Ok(None);
1167        }
1168
1169        let full = resp.bytes().await?;
1170
1171        if full.is_empty() {
1172            return Ok(Some(vec![]));
1173        }
1174
1175        Ok(Some(full.to_vec()))
1176    }
1177
1178    /// Create/Update Key
1179    /// This endpoint updates the value of the specified key. If no key exists
1180    /// at the given path, the key will be created.
1181    pub async fn kv_create_or_update_key<S: Into<String>>(
1182        &self,
1183        key: S,
1184        b: Vec<u8>,
1185        q: &KVCreateOrUpdateKeyQuery,
1186    ) -> Result<bool> {
1187        let path = format!("/kv/{}", key.into());
1188        let resp = self
1189            .execute_request(Method::PUT, &path, q, Some(b), &())
1190            .await?;
1191        resp.json().await.map_err(|e| anyhow!(e))
1192    }
1193
1194    /// Delete Key
1195    /// This endpoint deletes a single key or all keys sharing a prefix.
1196    pub async fn kv_delete_key<S: Into<String>>(
1197        &self,
1198        key: S,
1199        q: &KVDeleteKeyQuery,
1200    ) -> Result<bool> {
1201        let path = format!("/kv/{}", key.into());
1202        let resp = self
1203            .execute_request(Method::DELETE, &path, q, None, &())
1204            .await?;
1205        resp.json().await.map_err(|e| anyhow!(e))
1206    }
1207
1208    /// Create a Namespace
1209    ///
1210    /// This feature requires Consul Enterprise.
1211    ///
1212    /// This endpoint creates a new Namespace.
1213    ///
1214    #[cfg(feature = "enterprise")]
1215    pub async fn namespace_create(&self, b: &NamespaceCreateBody) -> Result<NamespaceDetail> {
1216        let resp = self
1217            .execute_request(Method::PUT, "/namespace", &(), None, b)
1218            .await?;
1219        resp.json().await.map_err(|e| anyhow!(e))
1220    }
1221
1222    /// Read a Namespace
1223    ///
1224    /// This feature requires Consul Enterprise.
1225    ///
1226    /// This endpoint reads a Namespace with the given name.
1227    ///
1228    #[cfg(feature = "enterprise")]
1229    pub async fn namespace_read<S: Into<String>>(
1230        &self,
1231        name: S,
1232        q: &NamespaceReadQuery,
1233    ) -> Result<Option<NamespaceDetail>> {
1234        let p = format!("/namespace/{}", name.into());
1235
1236        let resp = self.execute_request(Method::GET, &p, &q, None, &()).await?;
1237
1238        if resp.status() == StatusCode::NOT_FOUND {
1239            return Ok(None);
1240        }
1241
1242        Ok(Some(resp.json().await.map_err(|e| anyhow!(e))?))
1243    }
1244
1245    /// Update a Namespace
1246    ///
1247    /// This feature requires Consul Enterprise.
1248    ///
1249    /// This endpoint reads a Namespace with the given name.
1250    ///
1251    #[cfg(feature = "enterprise")]
1252    pub async fn namespace_update<S: Into<String>>(
1253        &self,
1254        name: S,
1255        b: &NamespaceUpdateBody,
1256    ) -> Result<NamespaceDetail> {
1257        let p = format!("/namespace/{}", name.into());
1258
1259        let resp = self.execute_request(Method::PUT, &p, &(), None, &b).await?;
1260
1261        resp.json().await.map_err(|e| anyhow!(e))
1262    }
1263
1264    /// Get Raft Leader
1265    ///
1266    /// This endpoint returns the Raft leader for the datacenter in which the
1267    /// agent is running.
1268    ///
1269    pub async fn status_leader(&self, q: &StatusQuery) -> Result<String> {
1270        let resp = self
1271            .execute_request(Method::GET, "/status/leader", q, None, &())
1272            .await?;
1273
1274        resp.text_with_charset("utf-8")
1275            .await
1276            .map_err(|e| anyhow!(e))
1277    }
1278
1279    /// List Raft Peers
1280    ///
1281    /// This endpoint retrieves the Raft peers for the datacenter in which the
1282    /// agent is running. This list of peers is strongly consistent and can be
1283    /// useful in determining when a given server has successfully joined the
1284    /// cluster.
1285    ///
1286    pub async fn status_peers(&self, q: &StatusQuery) -> Result<Vec<String>> {
1287        let resp = self
1288            .execute_request(Method::GET, "/status/peers", q, None, &())
1289            .await?;
1290
1291        resp.json().await.map_err(|e| anyhow!(e))
1292    }
1293
1294    async fn execute_request<Q, B>(
1295        &self,
1296        method: Method,
1297        path: &str,
1298        query: &Q,
1299        raw_body: Option<Vec<u8>>,
1300        json_body: &B,
1301    ) -> Result<Response>
1302    where
1303        Q: Serialize,
1304        B: Serialize,
1305    {
1306        let path = format!("{}{}{}", self.cfg.address, self.prefix, path);
1307        let mut b = self.http.request(method.clone(), &path);
1308
1309        b = b.query(query);
1310
1311        if method == Method::PUT || method == Method::POST {
1312            if let Some(body) = raw_body {
1313                b = b.body(body)
1314            } else {
1315                b = b.json(json_body);
1316            }
1317        }
1318
1319        let resp = b.send().await?;
1320        Ok(resp)
1321    }
1322}
1323
1324#[inline]
1325fn read_env_or_default(key: &str, default: &str) -> String {
1326    std::env::var(key).unwrap_or_else(|_| default.to_string())
1327}
1328
1329#[cfg(test)]
1330mod tests {
1331    use super::*;
1332
1333    #[test]
1334    fn create_client() {
1335        let _ = Client::new();
1336    }
1337}