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 super::DEFAULT_SCHEME;
30use crate::raw::*;
31use crate::services::ghac::core::GhacCore;
32use crate::services::GhacConfig;
33use crate::*;
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 type Config = GhacConfig;
140
141 fn build(self) -> Result<impl Access> {
142 debug!("backend build started: {self:?}");
143
144 let root = normalize_root(&self.config.root.unwrap_or_default());
145 debug!("backend use root {root}");
146
147 let service_version = get_cache_service_version();
148 debug!("backend use service version {service_version:?}");
149
150 let mut version = self
151 .config
152 .version
153 .clone()
154 .unwrap_or_else(|| "opendal".to_string());
155 debug!("backend use version {version}");
156 if matches!(service_version, GhacVersion::V2) {
158 let hash = sha2::Sha256::digest(&version);
159 version = format!("{hash:x}");
160 }
161
162 let cache_url = self
163 .config
164 .endpoint
165 .unwrap_or_else(|| get_cache_service_url(service_version));
166 if cache_url.is_empty() {
167 return Err(Error::new(
168 ErrorKind::ConfigInvalid,
169 "cache url for ghac not found, maybe not in github action environment?".to_string(),
170 ));
171 }
172
173 let core = GhacCore {
174 info: {
175 let am = AccessorInfo::default();
176 am.set_scheme(DEFAULT_SCHEME)
177 .set_root(&root)
178 .set_name(&version)
179 .set_native_capability(Capability {
180 stat: true,
181
182 read: true,
183
184 write: true,
185 write_can_multi: true,
186
187 shared: true,
188
189 ..Default::default()
190 });
191
192 #[allow(deprecated)]
194 if let Some(client) = self.http_client {
195 am.update_http_client(|_| client);
196 }
197
198 am.into()
199 },
200 root,
201
202 cache_url,
203 catch_token: value_or_env(
204 self.config.runtime_token,
205 ACTIONS_RUNTIME_TOKEN,
206 "Builder::build",
207 )?,
208 version,
209
210 service_version,
211 };
212
213 Ok(GhacBackend {
214 core: Arc::new(core),
215 })
216 }
217}
218
219#[derive(Debug, Clone)]
221pub struct GhacBackend {
222 core: Arc<GhacCore>,
223}
224
225impl Access for GhacBackend {
226 type Reader = HttpBody;
227 type Writer = GhacWriter;
228 type Lister = ();
229 type Deleter = ();
230
231 fn info(&self) -> Arc<AccessorInfo> {
232 self.core.info.clone()
233 }
234
235 async fn stat(&self, path: &str, _: OpStat) -> Result<RpStat> {
241 let resp = self.core.ghac_stat(path).await?;
242
243 let status = resp.status();
244 match status {
245 StatusCode::OK | StatusCode::PARTIAL_CONTENT | StatusCode::RANGE_NOT_SATISFIABLE => {
246 let mut meta = parse_into_metadata(path, resp.headers())?;
247 meta.set_content_length(
249 meta.content_range()
250 .expect("content range must be valid")
251 .size()
252 .expect("content range must contains size"),
253 );
254
255 Ok(RpStat::new(meta))
256 }
257 _ => Err(parse_error(resp)),
258 }
259 }
260
261 async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
262 let resp = self.core.ghac_read(path, args.range()).await?;
263
264 let status = resp.status();
265 match status {
266 StatusCode::OK | StatusCode::PARTIAL_CONTENT => {
267 Ok((RpRead::default(), resp.into_body()))
268 }
269 _ => {
270 let (part, mut body) = resp.into_parts();
271 let buf = body.to_buffer().await?;
272 Err(parse_error(Response::from_parts(part, buf)))
273 }
274 }
275 }
276
277 async fn write(&self, path: &str, _: OpWrite) -> Result<(RpWrite, Self::Writer)> {
278 let url = self.core.ghac_get_upload_url(path).await?;
279
280 Ok((
281 RpWrite::default(),
282 GhacWriter::new(self.core.clone(), path.to_string(), url)?,
283 ))
284 }
285}