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