rebuilderd_common/api/
mod.rs1use crate::auth;
2use crate::config::ConfigFile;
3use crate::errors::Error;
4use crate::utils::zstd_compress;
5use anyhow::{Context, anyhow};
6use async_trait::async_trait;
7use log::debug;
8use reqwest::header::CONTENT_ENCODING;
9use reqwest::{RequestBuilder, Response};
10use std::borrow::Cow;
11use std::env;
12use url::Url;
13
14pub mod v0;
15pub mod v1;
16
17pub const AUTH_COOKIE_HEADER: &str = "X-Auth-Cookie";
18pub const WORKER_KEY_HEADER: &str = "X-Worker-Key";
19pub const SIGNUP_SECRET_HEADER: &str = "X-Signup-Secret";
20
21pub struct Client {
22 endpoint: Url,
23 client: crate::http::Client,
24 is_default_endpoint: bool,
25 auth_cookie: Option<String>,
26 worker_key: Option<String>,
27 signup_secret: Option<String>,
28}
29
30impl Client {
31 pub fn new(config: ConfigFile, endpoint: Option<String>) -> anyhow::Result<Client> {
32 let (endpoint, auth_cookie, is_default_endpoint) = if let Some(endpoint) = endpoint {
33 let cookie = config
34 .endpoints
35 .get(&endpoint)
36 .map(|e| e.cookie.to_string());
37 (endpoint, cookie, false)
38 } else if let Some(endpoint) = config.http.endpoint {
39 (endpoint, None, true)
40 } else {
41 ("http://127.0.0.1:8484".to_string(), None, true)
42 };
43
44 let mut endpoint = endpoint
45 .parse::<Url>()
46 .with_context(|| anyhow!("Failed to parse endpoint as url: {:?}", endpoint))?;
47
48 endpoint
50 .path_segments_mut()
51 .map_err(|_| anyhow!("Given endpoint url cannot be base"))?
52 .pop_if_empty();
53
54 debug!("Setting rebuilderd endpoint to {:?}", endpoint.as_str());
55 let client = crate::http::Client::builder().zstd(true).build()?;
56
57 Ok(Client {
58 endpoint,
59 client,
60 is_default_endpoint,
61 auth_cookie,
62 worker_key: None,
63 signup_secret: None,
64 })
65 }
66
67 pub fn with_auth_cookie(&mut self) -> anyhow::Result<&mut Self> {
68 if let Ok(cookie_path) = env::var("REBUILDERD_COOKIE_PATH") {
69 debug!("Found cookie path in environment: {:?}", cookie_path);
70 let auth_cookie =
71 auth::read_cookie_from_file(cookie_path).context("Failed to load auth cookie")?;
72 Ok(self.auth_cookie(auth_cookie))
73 } else if self.is_default_endpoint {
74 let auth_cookie = auth::find_auth_cookie().context("Failed to load auth cookie")?;
75 Ok(self.auth_cookie(auth_cookie))
76 } else {
77 Ok(self)
78 }
79 }
80
81 pub fn auth_cookie<I: Into<String>>(&mut self, cookie: I) -> &mut Self {
82 self.auth_cookie = Some(cookie.into());
83 self
84 }
85
86 pub fn worker_key<I: Into<String>>(&mut self, key: I) {
87 self.worker_key = Some(key.into());
88 }
89
90 pub fn signup_secret<I: Into<String>>(&mut self, secret: I) {
91 self.signup_secret = Some(secret.into());
92 }
93
94 fn url_join(&self, route: &str) -> Url {
95 let mut url = self.endpoint.clone();
96 {
97 let mut path = url.path_segments_mut().expect("Url cannot be base");
99 for segment in route.split('/') {
100 path.push(segment);
101 }
102 }
103
104 url
105 }
106
107 fn authenticated(&self, mut req: RequestBuilder) -> RequestBuilder {
108 if let Some(auth_cookie) = &self.auth_cookie {
109 req = req.header(AUTH_COOKIE_HEADER, auth_cookie);
110 }
111
112 if let Some(worker_key) = &self.worker_key {
113 req = req.header(WORKER_KEY_HEADER, worker_key);
114 }
115
116 if let Some(signup_secret) = &self.signup_secret {
117 req = req.header(SIGNUP_SECRET_HEADER, signup_secret);
118 }
119
120 req
121 }
122
123 fn get(&self, path: Cow<'static, str>) -> crate::http::RequestBuilder {
124 let url = self.url_join(&path);
125 debug!("Sending GET request to {}", url.as_str());
126 let req = self.client.get(url);
127 self.authenticated(req)
128 }
129
130 fn post(&self, path: Cow<'static, str>) -> crate::http::RequestBuilder {
131 let url = self.url_join(&path);
132 debug!("Sending POST request to {}", url.as_str());
133 let req = self.client.post(url);
134 self.authenticated(req)
135 }
136
137 fn delete(&self, path: Cow<'static, str>) -> crate::http::RequestBuilder {
138 let url = self.url_join(&path);
139 debug!("Sending DELETE request to {}", url.as_str());
140 let req = self.client.delete(url);
141 self.authenticated(req)
142 }
143}
144
145#[async_trait]
146pub trait ZstdRequestBuilder {
147 async fn send_encoded(self) -> crate::errors::Result<Response>;
148}
149
150#[async_trait]
151impl ZstdRequestBuilder for RequestBuilder {
152 async fn send_encoded(self) -> crate::errors::Result<Response> {
153 if let Some(new_request) = self.try_clone() {
154 let mut request = self.build()?;
155
156 if let Some(body) = request.body_mut() {
157 if let Some(bytes) = body.as_bytes() {
158 let encoded_body = zstd_compress(bytes).await?;
159
160 new_request
161 .body(encoded_body)
162 .header(CONTENT_ENCODING, "zstd")
163 .send()
164 .await
165 .map_err(Error::from)
166 } else {
167 new_request.send().await.map_err(Error::from)
168 }
169 } else {
170 new_request.send().await.map_err(Error::from)
171 }
172 } else {
173 self.send().await.map_err(Error::from)
174 }
175 }
176}