opendal/services/ghac/
backend.rs1use std::env;
19use std::fmt::Debug;
20use std::sync::Arc;
21
22use http::Response;
23use http::StatusCode;
24use log::debug;
25use sha2::Digest;
26
27use super::GHAC_SCHEME;
28use super::config::GhacConfig;
29use super::core::GhacCore;
30use super::core::*;
31use super::error::parse_error;
32use super::writer::GhacWriter;
33use crate::raw::*;
34use crate::*;
35
36fn value_or_env(
37 explicit_value: Option<String>,
38 env_var_name: &str,
39 operation: &'static str,
40) -> Result<String> {
41 if let Some(value) = explicit_value {
42 return Ok(value);
43 }
44
45 env::var(env_var_name).map_err(|err| {
46 let text = format!("{env_var_name} not found, maybe not in github action environment?");
47 Error::new(ErrorKind::ConfigInvalid, text)
48 .with_operation(operation)
49 .set_source(err)
50 })
51}
52
53#[doc = include_str!("docs.md")]
55#[derive(Default)]
56pub struct GhacBuilder {
57 pub(super) config: GhacConfig,
58
59 #[deprecated(since = "0.53.0", note = "Use `Operator::update_http_client` instead")]
60 pub(super) http_client: Option<HttpClient>,
61}
62
63impl Debug for GhacBuilder {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 f.debug_struct("GhacBuilder")
66 .field("config", &self.config)
67 .finish_non_exhaustive()
68 }
69}
70
71impl GhacBuilder {
72 pub fn root(mut self, root: &str) -> Self {
74 self.config.root = if root.is_empty() {
75 None
76 } else {
77 Some(root.to_string())
78 };
79
80 self
81 }
82
83 pub fn version(mut self, version: &str) -> Self {
90 if !version.is_empty() {
91 self.config.version = Some(version.to_string())
92 }
93
94 self
95 }
96
97 pub fn endpoint(mut self, endpoint: &str) -> Self {
103 if !endpoint.is_empty() {
104 self.config.endpoint = Some(endpoint.to_string())
105 }
106 self
107 }
108
109 pub fn runtime_token(mut self, runtime_token: &str) -> Self {
116 if !runtime_token.is_empty() {
117 self.config.runtime_token = Some(runtime_token.to_string())
118 }
119 self
120 }
121
122 #[deprecated(since = "0.53.0", note = "Use `Operator::update_http_client` instead")]
129 #[allow(deprecated)]
130 pub fn http_client(mut self, client: HttpClient) -> Self {
131 self.http_client = Some(client);
132 self
133 }
134}
135
136impl Builder for GhacBuilder {
137 type Config = GhacConfig;
138
139 fn build(self) -> Result<impl Access> {
140 debug!("backend build started: {self:?}");
141
142 let root = normalize_root(&self.config.root.unwrap_or_default());
143 debug!("backend use root {root}");
144
145 let service_version = get_cache_service_version();
146 debug!("backend use service version {service_version:?}");
147
148 let mut version = self
149 .config
150 .version
151 .clone()
152 .unwrap_or_else(|| "opendal".to_string());
153 debug!("backend use version {version}");
154 if matches!(service_version, GhacVersion::V2) {
156 let hash = sha2::Sha256::digest(&version);
157 version = format!("{hash:x}");
158 }
159
160 let cache_url = self
161 .config
162 .endpoint
163 .unwrap_or_else(|| get_cache_service_url(service_version));
164 if cache_url.is_empty() {
165 return Err(Error::new(
166 ErrorKind::ConfigInvalid,
167 "cache url for ghac not found, maybe not in github action environment?".to_string(),
168 ));
169 }
170
171 let core = GhacCore {
172 info: {
173 let am = AccessorInfo::default();
174 am.set_scheme(GHAC_SCHEME)
175 .set_root(&root)
176 .set_name(&version)
177 .set_native_capability(Capability {
178 stat: true,
179
180 read: true,
181
182 write: true,
183 write_can_multi: true,
184
185 shared: true,
186
187 ..Default::default()
188 });
189
190 #[allow(deprecated)]
192 if let Some(client) = self.http_client {
193 am.update_http_client(|_| client);
194 }
195
196 am.into()
197 },
198 root,
199
200 cache_url,
201 catch_token: value_or_env(
202 self.config.runtime_token,
203 ACTIONS_RUNTIME_TOKEN,
204 "Builder::build",
205 )?,
206 version,
207
208 service_version,
209 };
210
211 Ok(GhacBackend {
212 core: Arc::new(core),
213 })
214 }
215}
216
217#[derive(Debug, Clone)]
219pub struct GhacBackend {
220 core: Arc<GhacCore>,
221}
222
223impl Access for GhacBackend {
224 type Reader = HttpBody;
225 type Writer = GhacWriter;
226 type Lister = ();
227 type Deleter = ();
228
229 fn info(&self) -> Arc<AccessorInfo> {
230 self.core.info.clone()
231 }
232
233 async fn stat(&self, path: &str, _: OpStat) -> Result<RpStat> {
239 let resp = self.core.ghac_stat(path).await?;
240
241 let status = resp.status();
242 match status {
243 StatusCode::OK | StatusCode::PARTIAL_CONTENT | StatusCode::RANGE_NOT_SATISFIABLE => {
244 let mut meta = parse_into_metadata(path, resp.headers())?;
245 meta.set_content_length(
247 meta.content_range()
248 .expect("content range must be valid")
249 .size()
250 .expect("content range must contains size"),
251 );
252
253 Ok(RpStat::new(meta))
254 }
255 _ => Err(parse_error(resp)),
256 }
257 }
258
259 async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
260 let resp = self.core.ghac_read(path, args.range()).await?;
261
262 let status = resp.status();
263 match status {
264 StatusCode::OK | StatusCode::PARTIAL_CONTENT => {
265 Ok((RpRead::default(), resp.into_body()))
266 }
267 _ => {
268 let (part, mut body) = resp.into_parts();
269 let buf = body.to_buffer().await?;
270 Err(parse_error(Response::from_parts(part, buf)))
271 }
272 }
273 }
274
275 async fn write(&self, path: &str, _: OpWrite) -> Result<(RpWrite, Self::Writer)> {
276 let url = self.core.ghac_get_upload_url(path).await?;
277
278 Ok((
279 RpWrite::default(),
280 GhacWriter::new(self.core.clone(), path.to_string(), url)?,
281 ))
282 }
283}