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