Skip to main content

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