icinga2_api/api/
blocking.rs1use std::{path::Path, str::from_utf8};
4
5use serde::{de::DeserializeOwned, Serialize};
6
7use crate::{
8 config::Icinga2Instance,
9 types::rest::{RestApiEndpoint, RestApiResponse},
10};
11
12#[derive(Debug, Clone)]
14pub struct Icinga2 {
15 client: reqwest::blocking::Client,
17 pub url: url::Url,
19 pub username: String,
21 password: String,
23}
24
25impl Icinga2 {
26 pub fn from_instance_config(config: &Icinga2Instance) -> Result<Self, crate::error::Error> {
33 let client_builder = reqwest::blocking::ClientBuilder::new();
34 let client_builder = client_builder.user_agent(concat!(
35 env!("CARGO_PKG_NAME"),
36 "/",
37 env!("CARGO_PKG_VERSION")
38 ));
39 let mut headers = reqwest::header::HeaderMap::new();
40 headers.insert(
41 "Content-Type",
42 reqwest::header::HeaderValue::from_static("application/json"),
43 );
44 headers.insert(
45 "Accept",
46 reqwest::header::HeaderValue::from_static("application/json"),
47 );
48 let client_builder = client_builder.default_headers(headers);
49 let client_builder = if let Some(ca_certificate) = &config.ca_certificate {
50 let ca_cert_content = std::fs::read(ca_certificate)
51 .map_err(crate::error::Error::CouldNotReadCACertFile)?;
52 let ca_cert = reqwest::Certificate::from_pem(&ca_cert_content)
53 .map_err(crate::error::Error::CouldNotParsePEMCACertificate)?;
54 client_builder.tls_certs_only([ca_cert])
55 } else {
56 client_builder
57 };
58 let client = client_builder
59 .build()
60 .map_err(crate::error::Error::CouldNotBuildReqwestClientFromSuppliedInformation)?;
61 let url =
62 url::Url::parse(&config.url).map_err(crate::error::Error::CouldNotParseUrlInConfig)?;
63 let username = config.username.clone();
64 let password = config.password.clone();
65 Ok(Icinga2 {
66 client,
67 url,
68 username,
69 password,
70 })
71 }
72
73 pub fn from_config_file(path: &Path) -> Result<Self, crate::error::Error> {
80 let icinga_instance = Icinga2Instance::from_config_file(path)?;
81 Self::from_instance_config(&icinga_instance)
82 }
83
84 pub fn rest<ApiEndpoint, Res>(
90 &self,
91 api_endpoint: ApiEndpoint,
92 ) -> Result<Res, crate::error::Error>
93 where
94 ApiEndpoint: RestApiEndpoint,
95 <ApiEndpoint as RestApiEndpoint>::RequestBody: Clone + Serialize + std::fmt::Debug,
96 Res: DeserializeOwned + std::fmt::Debug + RestApiResponse<ApiEndpoint>,
97 {
98 let method = api_endpoint.method()?;
99 let url = api_endpoint.url(&self.url)?;
100 let request_body: Option<std::borrow::Cow<<ApiEndpoint as RestApiEndpoint>::RequestBody>> =
101 api_endpoint.request_body()?;
102 let actual_method = if method == reqwest::Method::GET && request_body.is_some() {
103 reqwest::Method::POST
104 } else {
105 method.to_owned()
106 };
107 let mut req = self.client.request(actual_method, url.to_owned());
108 if method == reqwest::Method::GET && request_body.is_some() {
109 tracing::trace!("Sending GET request with body as POST via X-HTTP-Method-Override");
110 req = req.header(
111 "X-HTTP-Method-Override",
112 reqwest::header::HeaderValue::from_static("GET"),
113 );
114 }
115 req = req.basic_auth(&self.username, Some(&self.password));
116 if let Some(request_body) = request_body {
117 tracing::trace!("Request body:\n{:#?}", request_body);
118 req = req.json(&request_body);
119 }
120 let result = req.send();
121 if let Err(ref e) = result {
122 tracing::error!(%url, %method, "Icinga2 send error: {:?}", e);
123 }
124 let result = result?;
125 let status = result.status();
126 let response_body = result.bytes()?;
127 match from_utf8(&response_body) {
128 Ok(response_body) => {
129 tracing::trace!("Response body:\n{}", &response_body);
130 }
131 Err(e) => {
132 tracing::trace!(
133 "Response body that could not be parsed as utf8 because of {}:\n{:?}",
134 &e,
135 &response_body
136 );
137 }
138 }
139 if status.is_client_error() {
140 tracing::error!(%url, %method, "Icinga2 status error (client error): {:?}", status);
141 } else if status.is_server_error() {
142 tracing::error!(%url, %method, "Icinga2 status error (server error): {:?}", status);
143 }
144 if response_body.is_empty() {
145 Err(crate::error::Error::EmptyResponseBody(status))
146 } else {
147 let jd = &mut serde_json::Deserializer::from_slice(&response_body);
148 match serde_path_to_error::deserialize(jd) {
149 Ok(response_body) => {
150 tracing::trace!("Parsed response body:\n{:#?}", response_body);
151 Ok(response_body)
152 }
153 Err(e) => {
154 let path = e.path();
155 tracing::error!("Parsing failed at path {}: {}", path.to_string(), e.inner());
156 if let Ok(response_body) = serde_json::from_slice(&response_body) {
157 let mut response_body: serde_json::Value = response_body;
158 for segment in path {
159 match (response_body, segment) {
160 (
161 serde_json::Value::Array(vs),
162 serde_path_to_error::Segment::Seq { index },
163 ) => {
164 if let Some(v) = vs.get(*index) {
165 response_body = v.to_owned();
166 } else {
167 return Err(e.into());
169 }
170 }
171 (
172 serde_json::Value::Object(m),
173 serde_path_to_error::Segment::Map { key },
174 ) => {
175 if let Some(v) = m.get(key) {
176 response_body = v.to_owned();
177 } else {
178 return Err(e.into());
180 }
181 }
182 _ => {
183 return Err(e.into());
185 }
186 }
187 }
188 tracing::error!("Value in location path references is: {}", response_body);
189 }
190 Err(e.into())
191 }
192 }
193 }
194 }
195}