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