1use std::env;
19use std::fmt::Debug;
20use std::sync::Arc;
21
22use http::StatusCode;
23use log::debug;
24use sha2::Digest;
25
26use super::GHAC_SCHEME;
27use super::config::GhacConfig;
28use super::core::GhacCore;
29use super::core::parse_error;
30use super::core::*;
31use super::reader::*;
32use super::writer::GhacLazyWriter;
33use opendal_core::raw::*;
34use opendal_core::*;
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
60impl Debug for GhacBuilder {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.debug_struct("GhacBuilder")
63 .field("config", &self.config)
64 .finish_non_exhaustive()
65 }
66}
67
68impl GhacBuilder {
69 pub fn root(mut self, root: &str) -> Self {
71 self.config.root = if root.is_empty() {
72 None
73 } else {
74 Some(root.to_string())
75 };
76
77 self
78 }
79
80 pub fn version(mut self, version: &str) -> Self {
87 if !version.is_empty() {
88 self.config.version = Some(version.to_string())
89 }
90
91 self
92 }
93
94 pub fn endpoint(mut self, endpoint: &str) -> Self {
100 if !endpoint.is_empty() {
101 self.config.endpoint = Some(endpoint.to_string())
102 }
103 self
104 }
105
106 pub fn runtime_token(mut self, runtime_token: &str) -> Self {
113 if !runtime_token.is_empty() {
114 self.config.runtime_token = Some(runtime_token.to_string())
115 }
116 self
117 }
118}
119
120impl Builder for GhacBuilder {
121 type Config = GhacConfig;
122
123 fn build(self) -> Result<impl Service> {
124 debug!("backend build started: {self:?}");
125
126 let root = normalize_root(&self.config.root.unwrap_or_default());
127 debug!("backend use root {root}");
128
129 let service_version = get_cache_service_version();
130 debug!("backend use service version {service_version:?}");
131
132 let mut version = self
133 .config
134 .version
135 .clone()
136 .unwrap_or_else(|| "opendal".to_string());
137 debug!("backend use version {version}");
138 if matches!(service_version, GhacVersion::V2) {
140 let hash = sha2::Sha256::digest(&version);
141 version = format_digest_hex(hash);
142 }
143
144 let cache_url = self
145 .config
146 .endpoint
147 .unwrap_or_else(|| get_cache_service_url(service_version));
148 if cache_url.is_empty() {
149 return Err(Error::new(
150 ErrorKind::ConfigInvalid,
151 "cache url for ghac not found, maybe not in github action environment?".to_string(),
152 ));
153 }
154
155 let core = GhacCore {
156 info: ServiceInfo::new(GHAC_SCHEME, &root, &version),
157 capability: Capability {
158 stat: true,
159
160 read: true,
161
162 write: true,
163 write_can_multi: true,
164
165 shared: true,
166
167 ..Default::default()
168 },
169 root,
170
171 cache_url,
172 catch_token: value_or_env(
173 self.config.runtime_token,
174 ACTIONS_RUNTIME_TOKEN,
175 "Builder::build",
176 )?,
177 version,
178
179 service_version,
180 };
181
182 Ok(GhacBackend {
183 core: Arc::new(core),
184 })
185 }
186}
187
188fn format_digest_hex(digest: impl AsRef<[u8]>) -> String {
189 use std::fmt::Write;
190
191 let digest = digest.as_ref();
192 let mut output = String::with_capacity(digest.len() * 2);
193 for byte in digest {
194 write!(&mut output, "{byte:02x}").expect("writing to String must succeed");
195 }
196 output
197}
198
199#[derive(Debug, Clone)]
201pub struct GhacBackend {
202 pub(crate) core: Arc<GhacCore>,
203}
204
205impl Service for GhacBackend {
206 type Reader = oio::StreamReader<GhacReader>;
207 type Writer = GhacLazyWriter;
208 type Lister = ();
209 type Deleter = ();
210 type Copier = ();
211
212 fn info(&self) -> ServiceInfo {
213 self.core.info.clone()
214 }
215
216 fn capability(&self) -> Capability {
217 self.core.capability
218 }
219
220 async fn create_dir(
221 &self,
222 _ctx: &OperationContext,
223 _path: &str,
224 _args: OpCreateDir,
225 ) -> Result<RpCreateDir> {
226 Err(Error::new(
227 ErrorKind::Unsupported,
228 "operation is not supported",
229 ))
230 }
231
232 async fn stat(&self, ctx: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
238 let resp = self.core.ghac_stat(ctx, path).await?;
239
240 let status = resp.status();
241 match status {
242 StatusCode::OK | StatusCode::PARTIAL_CONTENT | StatusCode::RANGE_NOT_SATISFIABLE => {
243 let meta = parse_into_metadata(path, resp.headers())?;
244 Ok(RpStat::new(meta))
245 }
246 _ => Err(parse_error(resp)),
247 }
248 }
249 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
250 let output: oio::StreamReader<GhacReader> = {
251 Ok(oio::StreamReader::new(GhacReader::new(
252 self.clone(),
253 ctx.clone(),
254 path,
255 args,
256 )))
257 }?;
258
259 Ok(output)
260 }
261
262 fn write(&self, ctx: &OperationContext, path: &str, _: OpWrite) -> Result<Self::Writer> {
263 Ok(GhacLazyWriter::new(
264 self.core.clone(),
265 ctx.clone(),
266 ctx.executor().clone(),
267 path.to_string(),
268 ))
269 }
270
271 fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
272 Err(Error::new(
273 ErrorKind::Unsupported,
274 "operation is not supported",
275 ))
276 }
277
278 fn list(&self, _ctx: &OperationContext, _path: &str, _args: OpList) -> Result<Self::Lister> {
279 Err(Error::new(
280 ErrorKind::Unsupported,
281 "operation is not supported",
282 ))
283 }
284
285 fn copy(
286 &self,
287 _ctx: &OperationContext,
288 _from: &str,
289 _to: &str,
290 _args: OpCopy,
291 _opts: OpCopier,
292 ) -> Result<Self::Copier> {
293 Err(Error::new(
294 ErrorKind::Unsupported,
295 "operation is not supported",
296 ))
297 }
298
299 async fn rename(
300 &self,
301 _ctx: &OperationContext,
302 _from: &str,
303 _to: &str,
304 _args: OpRename,
305 ) -> Result<RpRename> {
306 Err(Error::new(
307 ErrorKind::Unsupported,
308 "operation is not supported",
309 ))
310 }
311
312 async fn presign(
313 &self,
314 _ctx: &OperationContext,
315 _path: &str,
316 _args: OpPresign,
317 ) -> Result<RpPresign> {
318 Err(Error::new(
319 ErrorKind::Unsupported,
320 "operation is not supported",
321 ))
322 }
323}