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 let client_builder = client_builder.add_root_certificate(ca_cert);
55 client_builder.tls_built_in_root_certs(false)
56 } else {
57 client_builder
58 };
59 let client = client_builder
60 .build()
61 .map_err(crate::error::Error::CouldNotBuildReqwestClientFromSuppliedInformation)?;
62 let url =
63 url::Url::parse(&config.url).map_err(crate::error::Error::CouldNotParseUrlInConfig)?;
64 let username = config.username.clone();
65 let password = config.password.clone();
66 Ok(Icinga2 {
67 client,
68 url,
69 username,
70 password,
71 })
72 }
73
74 pub fn from_config_file(path: &Path) -> Result<Self, crate::error::Error> {
81 let icinga_instance = Icinga2Instance::from_config_file(path)?;
82 Self::from_instance_config(&icinga_instance)
83 }
84
85 pub fn rest<ApiEndpoint, Res>(
91 &self,
92 api_endpoint: ApiEndpoint,
93 ) -> Result<Res, crate::error::Error>
94 where
95 ApiEndpoint: RestApiEndpoint,
96 <ApiEndpoint as RestApiEndpoint>::RequestBody: Clone + Serialize + std::fmt::Debug,
97 Res: DeserializeOwned + std::fmt::Debug + RestApiResponse<ApiEndpoint>,
98 {
99 let method = api_endpoint.method()?;
100 let url = api_endpoint.url(&self.url)?;
101 let request_body: Option<std::borrow::Cow<<ApiEndpoint as RestApiEndpoint>::RequestBody>> =
102 api_endpoint.request_body()?;
103 let actual_method = if method == reqwest::Method::GET && request_body.is_some() {
104 reqwest::Method::POST
105 } else {
106 method.to_owned()
107 };
108 let mut req = self.client.request(actual_method, url.to_owned());
109 if method == reqwest::Method::GET && request_body.is_some() {
110 tracing::trace!("Sending GET request with body as POST via X-HTTP-Method-Override");
111 req = req.header(
112 "X-HTTP-Method-Override",
113 reqwest::header::HeaderValue::from_static("GET"),
114 );
115 }
116 req = req.basic_auth(&self.username, Some(&self.password));
117 if let Some(request_body) = request_body {
118 tracing::trace!("Request body:\n{:#?}", request_body);
119 req = req.json(&request_body);
120 }
121 let result = req.send();
122 if let Err(ref e) = result {
123 tracing::error!(%url, %method, "Icinga2 send error: {:?}", e);
124 }
125 let result = result?;
126 let status = result.status();
127 let response_body = result.bytes()?;
128 match from_utf8(&response_body) {
129 Ok(response_body) => {
130 tracing::trace!("Response body:\n{}", &response_body);
131 }
132 Err(e) => {
133 tracing::trace!(
134 "Response body that could not be parsed as utf8 because of {}:\n{:?}",
135 &e,
136 &response_body
137 );
138 }
139 }
140 if status.is_client_error() {
141 tracing::error!(%url, %method, "Icinga2 status error (client error): {:?}", status);
142 } else if status.is_server_error() {
143 tracing::error!(%url, %method, "Icinga2 status error (server error): {:?}", status);
144 }
145 if response_body.is_empty() {
146 Err(crate::error::Error::EmptyResponseBody(status))
147 } else {
148 let jd = &mut serde_json::Deserializer::from_slice(&response_body);
149 match serde_path_to_error::deserialize(jd) {
150 Ok(response_body) => {
151 tracing::trace!("Parsed response body:\n{:#?}", response_body);
152 Ok(response_body)
153 }
154 Err(e) => {
155 let path = e.path();
156 tracing::error!("Parsing failed at path {}: {}", path.to_string(), e.inner());
157 if let Ok(response_body) = serde_json::from_slice(&response_body) {
158 let mut response_body: serde_json::Value = response_body;
159 for segment in path {
160 match (response_body, segment) {
161 (
162 serde_json::Value::Array(vs),
163 serde_path_to_error::Segment::Seq { index },
164 ) => {
165 if let Some(v) = vs.get(*index) {
166 response_body = v.to_owned();
167 } else {
168 return Err(e.into());
170 }
171 }
172 (
173 serde_json::Value::Object(m),
174 serde_path_to_error::Segment::Map { key },
175 ) => {
176 if let Some(v) = m.get(key) {
177 response_body = v.to_owned();
178 } else {
179 return Err(e.into());
181 }
182 }
183 _ => {
184 return Err(e.into());
186 }
187 }
188 }
189 tracing::error!("Value in location path references is: {}", response_body);
190 }
191 Err(e.into())
192 }
193 }
194 }
195 }
196}