Author:
Hash:
Timestamp:
+295 -109 +/-7 browse
Kevin Schoon [me@kevinschoon.com]
c3812251654ec0d3108f68ed22cebc393fce8c25
Tue, 15 Apr 2025 16:15:19 +0000 (1 month ago)
1 | diff --git a/README.md b/README.md |
2 | index 9b6b5be..09eb6a3 100644 |
3 | --- a/README.md |
4 | +++ b/README.md |
5 | @@ -9,6 +9,11 @@ be implemented in the future. This library is intended to be used in |
6 | |
7 | Note that this code has only been tested for use with Podman. |
8 | |
9 | + ## OCI Distribution Test |
10 | + |
11 | + The distribution spec contains a compliance test that is package in the form of a container. |
12 | + Build the container provided [here](https://github.com/opencontainers/distribution-spec/tree/main/conformance) |
13 | + and then run `scripts/conformance_test.sh`. Results are saved in the `results/` directory. |
14 | |
15 | ## Running the Example |
16 | |
17 | @@ -31,4 +36,5 @@ And finally interact with the dev server: |
18 | podman login localhost:8700 |
19 | podman tag alpine:3 localhost:8700/alpine:3 |
20 | podman push localhost:8700/alpine:3 |
21 | + podman pull localhost:8700/alpine:3 |
22 | ``` |
23 | diff --git a/src/address.rs b/src/address.rs |
24 | index 18b3690..88347a5 100644 |
25 | --- a/src/address.rs |
26 | +++ b/src/address.rs |
27 | @@ -1,15 +1,15 @@ |
28 | use std::{ |
29 | + collections::VecDeque, |
30 | fmt::Display, |
31 | path::{Path, PathBuf}, |
32 | + str::FromStr, |
33 | }; |
34 | |
35 | use oci_spec::image::Digest; |
36 | use relative_path::RelativePath; |
37 | use uuid::Uuid; |
38 | |
39 | - use crate::Namespace; |
40 | - |
41 | - const SEPARATOR: &str = "/"; |
42 | + use crate::{Namespace, SEPARATOR}; |
43 | |
44 | fn digest_prefix(digest: &Digest) -> &str { |
45 | match digest.algorithm() { |
46 | @@ -21,42 +21,43 @@ fn digest_prefix(digest: &Digest) -> &str { |
47 | } |
48 | } |
49 | |
50 | - pub enum Address<'a> { |
51 | + #[derive(Debug, PartialEq, Eq)] |
52 | + pub enum Address { |
53 | Tag { |
54 | - namespace: &'a Namespace, |
55 | - name: &'a str, |
56 | + namespace: Namespace, |
57 | + name: String, |
58 | }, |
59 | TagDirectory { |
60 | - namespace: &'a Namespace, |
61 | + namespace: Namespace, |
62 | }, |
63 | Reference { |
64 | - namespace: &'a Namespace, |
65 | - digest: &'a Digest, |
66 | + namespace: Namespace, |
67 | + digest: Digest, |
68 | }, |
69 | TempBlob { |
70 | - uuid: &'a Uuid, |
71 | - namespace: &'a Namespace, |
72 | + uuid: Uuid, |
73 | + namespace: Namespace, |
74 | }, |
75 | Blob { |
76 | - digest: &'a Digest, |
77 | + digest: Digest, |
78 | }, |
79 | ManifestRevision { |
80 | - namespace: &'a Namespace, |
81 | - digest: &'a Digest, |
82 | + namespace: Namespace, |
83 | + digest: Digest, |
84 | }, |
85 | LayerLink { |
86 | - namespace: &'a Namespace, |
87 | - digest: &'a Digest, |
88 | + namespace: Namespace, |
89 | + digest: Digest, |
90 | }, |
91 | } |
92 | |
93 | - impl Display for Address<'_> { |
94 | + impl Display for Address { |
95 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
96 | write!(f, "{}", self.components().join(SEPARATOR)) |
97 | } |
98 | } |
99 | |
100 | - impl Address<'_> { |
101 | + impl Address { |
102 | pub fn path(&self, base_dir: &Path) -> PathBuf { |
103 | let parts = self.components().join(SEPARATOR); |
104 | RelativePath::new(&parts).to_path(base_dir) |
105 | @@ -101,7 +102,7 @@ impl Address<'_> { |
106 | let first_two: String = digest_str.chars().take(2).collect(); |
107 | Vec::from_iter( |
108 | format!( |
109 | - "blobs/{}/{}/{}", |
110 | + "blobs/{}/{}/{}/data", |
111 | digest_prefix(digest), |
112 | first_two, |
113 | digest_str |
114 | @@ -134,54 +135,155 @@ impl Address<'_> { |
115 | } |
116 | } |
117 | |
118 | + impl FromStr for Address { |
119 | + type Err = crate::error::Error; |
120 | + |
121 | + fn from_str(s: &str) -> Result<Self, Self::Err> { |
122 | + let mut split: VecDeque<String> = s.split(SEPARATOR).map(|part| part.to_string()).collect(); |
123 | + let n_components = split.len(); |
124 | + if n_components < 3 { |
125 | + return Err(crate::error::Error::UnparsableAddress(s.to_string())); |
126 | + } |
127 | + let first = split.pop_front().unwrap(); |
128 | + match first.as_str() { |
129 | + "blobs" => { |
130 | + // example: sha256/57/57f2ae062b76cff6f5a511fe6f907decfdefd6495e6afa31c44e0a6a1eca146f/data |
131 | + let algorithm = split.pop_front().unwrap(); |
132 | + split.pop_front(); |
133 | + let digest = split.pop_front().unwrap(); |
134 | + let digest = Digest::from_str(&format!("{}:{}", algorithm, digest))?; |
135 | + Ok(Address::Blob { digest }) |
136 | + } |
137 | + "repositories" => { |
138 | + if s.ends_with("tags") { |
139 | + // example: /{namespace}/tags |
140 | + split.pop_back(); |
141 | + let namespace = Namespace::try_from( |
142 | + split.iter().cloned().collect::<Vec<String>>().as_slice(), |
143 | + )?; |
144 | + Ok(Address::TagDirectory { namespace }) |
145 | + } else if s.ends_with("current/link") { |
146 | + // example: /{namespace}/tags/{name}/current/link |
147 | + let name = split.get(split.len() - 3).cloned().unwrap(); |
148 | + split.truncate(split.len() - 4); |
149 | + let namespace = Namespace::try_from( |
150 | + split.iter().cloned().collect::<Vec<String>>().as_slice(), |
151 | + )?; |
152 | + Ok(Address::Tag { namespace, name }) |
153 | + } else if split.get(split.len() - 2).is_some_and(|item| item == "tmp") { |
154 | + // example: /hello/world/tmp/614f0a00-169e-4586-a6ed-cc52894130ae |
155 | + let uuid = Uuid::from_str(&split.pop_back().unwrap())?; |
156 | + split.pop_back(); |
157 | + let namespace = Namespace::try_from( |
158 | + split.iter().cloned().collect::<Vec<String>>().as_slice(), |
159 | + )?; |
160 | + Ok(Address::TempBlob { uuid, namespace }) |
161 | + } else if split |
162 | + .get(split.len() - 4) |
163 | + .is_some_and(|item| item == "revisions") |
164 | + { |
165 | + // example: /hello/world/manifests/revisions/sha256/57f2ae062b76cff6f5a511fe6f907decfdefd6495e6afa31c44e0a6a1eca146f/link |
166 | + let alogrithm = split.get(split.len() - 3).unwrap(); |
167 | + let digest = split.get(split.len() - 2).unwrap(); |
168 | + let digest = Digest::from_str(&format!("{}:{}", alogrithm, digest))?; |
169 | + split.truncate(split.len() - 5); |
170 | + let namespace = Namespace::try_from( |
171 | + split.iter().cloned().collect::<Vec<String>>().as_slice(), |
172 | + )?; |
173 | + Ok(Address::ManifestRevision { namespace, digest }) |
174 | + } else { |
175 | + Err(crate::error::Error::UnparsableAddress(s.to_string())) |
176 | + } |
177 | + } |
178 | + _ => Err(crate::error::Error::UnparsableAddress(s.to_string())), |
179 | + } |
180 | + } |
181 | + } |
182 | + |
183 | #[cfg(test)] |
184 | mod test { |
185 | use std::str::FromStr; |
186 | |
187 | use super::*; |
188 | |
189 | - #[test] |
190 | - pub fn addresses() { |
191 | - let namespace = Namespace::from_str("hello/world").unwrap(); |
192 | - assert!( |
193 | - Address::TagDirectory { |
194 | - namespace: &namespace |
195 | + fn check_address(addr: &Address, expected: &str) { |
196 | + let actual = addr.to_string(); |
197 | + if actual != expected { |
198 | + panic!("Expected: '{}', got: '{}'", expected, actual); |
199 | + } |
200 | + let reverse: Result<Address, crate::error::Error> = expected.parse(); |
201 | + match reverse { |
202 | + Ok(reversed_addr) => { |
203 | + if !reversed_addr.eq(addr) { |
204 | + panic!("{:?} != {:?}", addr, reversed_addr) |
205 | + } |
206 | } |
207 | - .to_string() |
208 | - == "repositories/hello/world/tags" |
209 | - ); |
210 | - assert!( |
211 | - Address::Tag { |
212 | - namespace: &namespace, |
213 | - name: "latest" |
214 | + Err(err) => { |
215 | + panic!("Cannot parse: {} into Address", err); |
216 | } |
217 | - .to_string() |
218 | - == "repositories/hello/world/tags/latest/current/link" |
219 | + } |
220 | + } |
221 | + |
222 | + #[test] |
223 | + pub fn addresses_to_string() { |
224 | + let namespace = &Namespace::from_str("hello/world").unwrap(); |
225 | + check_address( |
226 | + &Address::TagDirectory { |
227 | + namespace: namespace.clone(), |
228 | + }, |
229 | + "repositories/hello/world/tags", |
230 | + ); |
231 | + check_address( |
232 | + &Address::Tag { |
233 | + namespace: namespace.clone(), |
234 | + name: String::from("latest"), |
235 | + }, |
236 | + "repositories/hello/world/tags/latest/current/link", |
237 | ); |
238 | let uuid = Uuid::new_v4(); |
239 | - assert!( |
240 | - Address::TempBlob { |
241 | - uuid: &uuid, |
242 | - namespace: &namespace |
243 | - } |
244 | - .to_string() |
245 | - == format!("repositories/hello/world/tmp/{}", uuid) |
246 | + check_address( |
247 | + &Address::TempBlob { |
248 | + uuid, |
249 | + namespace: namespace.clone(), |
250 | + }, |
251 | + &format!("repositories/hello/world/tmp/{}", uuid), |
252 | ); |
253 | let digest = Digest::from_str( |
254 | "sha256:57f2ae062b76cff6f5a511fe6f907decfdefd6495e6afa31c44e0a6a1eca146f", |
255 | ) |
256 | .unwrap(); |
257 | - assert!( |
258 | - Address::Blob { digest: &digest }.to_string() |
259 | - == "blobs/sha256/57/57f2ae062b76cff6f5a511fe6f907decfdefd6495e6afa31c44e0a6a1eca146f" |
260 | + check_address( |
261 | + &Address::Blob { |
262 | + digest: digest.clone(), |
263 | + }, |
264 | + "blobs/sha256/57/57f2ae062b76cff6f5a511fe6f907decfdefd6495e6afa31c44e0a6a1eca146f/data", |
265 | + ); |
266 | + check_address( |
267 | + &Address::ManifestRevision { |
268 | + namespace: namespace.clone(), |
269 | + digest: digest.clone(), |
270 | + }, |
271 | + "repositories/hello/world/manifests/revisions/sha256/57f2ae062b76cff6f5a511fe6f907decfdefd6495e6afa31c44e0a6a1eca146f/link", |
272 | ); |
273 | - assert!( |
274 | - Address::ManifestRevision { |
275 | - namespace: &namespace, |
276 | - digest: &digest |
277 | + |
278 | + ["repositories/hello/world/fuu/bar/tags", "repositories/hello/world///fuu/bar/tags"] |
279 | + .iter() |
280 | + .for_each(|item| { |
281 | + if let Err(err) = Address::from_str(item) { |
282 | + panic!("Cannot parse {}, got: {}", item, err) |
283 | + } |
284 | + }); |
285 | + |
286 | + [ |
287 | + "repositories/tags", |
288 | + "repositories/__/revisions/INVALID/INVALID/link", |
289 | + ] |
290 | + .iter() |
291 | + .for_each(|item| { |
292 | + if let Ok(addr) = Address::from_str(item) { |
293 | + panic!("Item {} should be unparsable, got: {}", item, addr) |
294 | } |
295 | - .to_string() |
296 | - == "repositories/hello/world/manifests/revisions/sha256/57f2ae062b76cff6f5a511fe6f907decfdefd6495e6afa31c44e0a6a1eca146f/link" |
297 | - ) |
298 | + }) |
299 | + // todo |
300 | } |
301 | } |
302 | diff --git a/src/error.rs b/src/error.rs |
303 | index 9f746eb..a6a96e1 100644 |
304 | --- a/src/error.rs |
305 | +++ b/src/error.rs |
306 | @@ -9,5 +9,9 @@ pub enum Error { |
307 | #[error("Namespace Invalid: {0}")] |
308 | Namespace(String), |
309 | #[error("Error parsing OCI Specification: {0}")] |
310 | - OciSpec(#[from] oci_spec::OciSpecError) |
311 | + OciSpec(#[from] oci_spec::OciSpecError), |
312 | + #[error("Cannot parse address from string: {0}")] |
313 | + UnparsableAddress(String), |
314 | + #[error("Invalid UUID: {0}")] |
315 | + Uuid(#[from] uuid::Error) |
316 | } |
317 | diff --git a/src/lib.rs b/src/lib.rs |
318 | index 46c898f..11acdaa 100644 |
319 | --- a/src/lib.rs |
320 | +++ b/src/lib.rs |
321 | @@ -1,9 +1,10 @@ |
322 | + use std::iter::{FromIterator, Iterator}; |
323 | use std::{fmt::Display, str::FromStr}; |
324 | |
325 | use error::Error; |
326 | pub use oci_spec::image::Digest; |
327 | use regex::Regex; |
328 | - use relative_path::RelativePath; |
329 | + use relative_path::{RelativePath, RelativePathBuf}; |
330 | |
331 | pub mod address; |
332 | pub mod error; |
333 | @@ -16,22 +17,40 @@ pub mod axum; |
334 | #[cfg(feature = "storage-fs")] |
335 | pub mod storage_fs; |
336 | |
337 | + pub const SEPARATOR: &str = "/"; |
338 | + |
339 | const NAME_REGEXP_MATCH: &str = |
340 | r"[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*(\/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*"; |
341 | |
342 | #[derive(Clone, Debug)] |
343 | pub enum TagOrDigest { |
344 | Tag(String), |
345 | - Digest(Digest) |
346 | + Digest(Digest), |
347 | } |
348 | |
349 | // TODO: Consider 255 char namespace limit - hostname length per spec docs |
350 | - #[derive(Clone)] |
351 | - pub struct Namespace(String); |
352 | + #[derive(Clone, Debug, PartialEq, Eq)] |
353 | + pub struct Namespace(Vec<String>); |
354 | |
355 | impl Namespace { |
356 | - pub fn path(&self) -> &RelativePath { |
357 | - RelativePath::new(&self.0) |
358 | + pub fn path(&self) -> RelativePathBuf { |
359 | + RelativePath::new(&self.0.join(SEPARATOR)).to_relative_path_buf() |
360 | + } |
361 | + } |
362 | + |
363 | + impl TryFrom<&[&str]> for Namespace { |
364 | + type Error = Error; |
365 | + |
366 | + fn try_from(value: &[&str]) -> Result<Self, Self::Error> { |
367 | + Namespace::from_str(value.join(SEPARATOR).as_str()) |
368 | + } |
369 | + } |
370 | + |
371 | + impl TryFrom<&[String]> for Namespace { |
372 | + type Error = Error; |
373 | + |
374 | + fn try_from(value: &[String]) -> Result<Self, Self::Error> { |
375 | + Namespace::from_str(value.join(SEPARATOR).as_str()) |
376 | } |
377 | } |
378 | |
379 | @@ -41,25 +60,36 @@ impl FromStr for Namespace { |
380 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
381 | let regexp = Regex::new(NAME_REGEXP_MATCH).unwrap(); |
382 | if regexp.is_match(s) { |
383 | - Ok(Namespace(s.to_string())) |
384 | + Ok(Namespace( |
385 | + s.split(SEPARATOR).map(|part| part.to_string()).collect(), |
386 | + )) |
387 | } else { |
388 | Err(Error::Namespace(s.to_string())) |
389 | } |
390 | } |
391 | } |
392 | |
393 | - impl Display for Namespace { |
394 | - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
395 | - write!(f, "{}", self.0) |
396 | + impl IntoIterator for Namespace { |
397 | + type Item = String; |
398 | + type IntoIter = std::vec::IntoIter<String>; |
399 | + |
400 | + fn into_iter(self) -> Self::IntoIter { |
401 | + self.0.into_iter() |
402 | } |
403 | } |
404 | |
405 | - impl AsRef<str> for Namespace { |
406 | - fn as_ref(&self) -> &str { |
407 | - &self.0 |
408 | + impl Display for Namespace { |
409 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
410 | + write!(f, "{}", self.0.join(SEPARATOR)) |
411 | } |
412 | } |
413 | |
414 | + // impl AsRef<str> for Namespace { |
415 | + // fn as_ref(&self) -> &str { |
416 | + // &self.0.join(SEPARATOR) |
417 | + // } |
418 | + // } |
419 | + |
420 | #[cfg(test)] |
421 | mod test { |
422 | use super::*; |
423 | @@ -70,5 +100,6 @@ mod test { |
424 | Namespace::from_str("fuu/bar").unwrap(); |
425 | Namespace::from_str("fuu/bar/baz/").unwrap(); |
426 | Namespace::from_str("fuu/bar/baz/qux").unwrap(); |
427 | + Namespace::try_from(vec!["fuu", "bar"].as_slice()).unwrap(); |
428 | } |
429 | } |
430 | diff --git a/src/oci_interface.rs b/src/oci_interface.rs |
431 | index d6b087b..4966940 100644 |
432 | --- a/src/oci_interface.rs |
433 | +++ b/src/oci_interface.rs |
434 | @@ -25,7 +25,7 @@ pub struct OciInterface { |
435 | } |
436 | |
437 | impl OciInterface { |
438 | - async fn resolve_link(&self, link: &Address<'_>) -> Result<Digest, Error> { |
439 | + async fn resolve_link(&self, link: &Address) -> Result<Digest, Error> { |
440 | if !link.is_link() { |
441 | panic!("Address is not a link: {}", link); |
442 | } |
443 | @@ -45,8 +45,8 @@ impl OciInterface { |
444 | self.storage |
445 | .write_all( |
446 | &Address::TempBlob { |
447 | - uuid: &uuid, |
448 | - namespace, |
449 | + uuid, |
450 | + namespace: namespace.clone(), |
451 | }, |
452 | &[], |
453 | ) |
454 | @@ -63,8 +63,13 @@ impl OciInterface { |
455 | ) -> Result<(), Error> { |
456 | self.storage |
457 | .mv( |
458 | - &Address::TempBlob { uuid, namespace }, |
459 | - &Address::Blob { digest }, |
460 | + &Address::TempBlob { |
461 | + uuid: *uuid, |
462 | + namespace: namespace.clone(), |
463 | + }, |
464 | + &Address::Blob { |
465 | + digest: digest.clone(), |
466 | + }, |
467 | ) |
468 | .await |
469 | .map_err(Error::Storage)?; |
470 | @@ -73,7 +78,10 @@ impl OciInterface { |
471 | |
472 | pub async fn delete_blob(&self, namespace: &Namespace, digest: &Digest) -> Result<(), Error> { |
473 | self.storage |
474 | - .delete(&Address::LayerLink { namespace, digest }) |
475 | + .delete(&Address::LayerLink { |
476 | + namespace: namespace.clone(), |
477 | + digest: digest.clone(), |
478 | + }) |
479 | .await |
480 | .map_err(Error::Storage)?; |
481 | Ok(()) |
482 | @@ -88,7 +96,10 @@ impl OciInterface { |
483 | where |
484 | S: Stream<Item = Result<Bytes, Error>>, |
485 | { |
486 | - let tmp_blob_addr = &Address::TempBlob { namespace, uuid }; |
487 | + let tmp_blob_addr = &Address::TempBlob { |
488 | + namespace: namespace.clone(), |
489 | + uuid: *uuid, |
490 | + }; |
491 | while let Some(item) = stream.next().await { |
492 | let chunk = item?.to_vec(); |
493 | self.storage |
494 | @@ -105,14 +116,22 @@ impl OciInterface { |
495 | digest: &Digest, |
496 | content: &[u8], |
497 | ) -> Result<(), Error> { |
498 | - let uuid = &Uuid::new_v4(); |
499 | - let tmp_blob_addr = &Address::TempBlob { namespace, uuid }; |
500 | + let uuid = Uuid::new_v4(); |
501 | + let tmp_blob_addr = &Address::TempBlob { |
502 | + namespace: namespace.clone(), |
503 | + uuid, |
504 | + }; |
505 | self.storage |
506 | .write_all(tmp_blob_addr, content) |
507 | .await |
508 | .map_err(Error::Storage)?; |
509 | self.storage |
510 | - .mv(tmp_blob_addr, &Address::Blob { digest }) |
511 | + .mv( |
512 | + tmp_blob_addr, |
513 | + &Address::Blob { |
514 | + digest: digest.clone(), |
515 | + }, |
516 | + ) |
517 | .await |
518 | .map_err(Error::Storage)?; |
519 | Ok(()) |
520 | @@ -127,8 +146,8 @@ impl OciInterface { |
521 | ) -> Result<(), Error> { |
522 | let uuid = Uuid::new_v4(); |
523 | let tmp_blob_addr = &Address::TempBlob { |
524 | - uuid: &uuid, |
525 | - namespace, |
526 | + uuid, |
527 | + namespace: namespace.clone(), |
528 | }; |
529 | self.storage |
530 | .write_all(tmp_blob_addr, manifest_bytes.to_vec().as_slice()) |
531 | @@ -138,7 +157,9 @@ impl OciInterface { |
532 | let hashed = Sha256::digest(manifest_bytes); |
533 | let hash_str = base16ct::lower::encode_string(&hashed); |
534 | let digest = Digest::from_str(&format!("sha256:{}", hash_str)).unwrap(); |
535 | - let blob_address = &Address::Blob { digest: &digest }; |
536 | + let blob_address = &Address::Blob { |
537 | + digest: digest.clone(), |
538 | + }; |
539 | self.storage |
540 | .mv(tmp_blob_addr, blob_address) |
541 | .await |
542 | @@ -146,8 +167,8 @@ impl OciInterface { |
543 | self.storage |
544 | .write_all( |
545 | &Address::Reference { |
546 | - namespace, |
547 | - digest: &digest, |
548 | + namespace: namespace.clone(), |
549 | + digest: digest.clone(), |
550 | }, |
551 | digest.to_string().as_bytes(), |
552 | ) |
553 | @@ -159,7 +180,10 @@ impl OciInterface { |
554 | let digest = layer.digest(); |
555 | let digest_str = digest.to_string(); |
556 | let digest_bytes = digest_str.as_bytes(); |
557 | - let layer_addr = Address::LayerLink { namespace, digest }; |
558 | + let layer_addr = Address::LayerLink { |
559 | + namespace: namespace.clone(), |
560 | + digest: digest.clone(), |
561 | + }; |
562 | self.storage |
563 | .write_all(&layer_addr, digest_bytes) |
564 | .await |
565 | @@ -173,7 +197,7 @@ impl OciInterface { |
566 | self.storage |
567 | .write_all( |
568 | &Address::Blob { |
569 | - digest: oci_config_digest, |
570 | + digest: oci_config_digest.clone(), |
571 | }, |
572 | decoded.as_slice(), |
573 | ) |
574 | @@ -182,8 +206,8 @@ impl OciInterface { |
575 | self.storage |
576 | .write_all( |
577 | &Address::LayerLink { |
578 | - namespace, |
579 | - digest: oci_config_digest, |
580 | + namespace: namespace.clone(), |
581 | + digest: oci_config_digest.clone(), |
582 | }, |
583 | decoded.as_slice(), |
584 | ) |
585 | @@ -194,7 +218,10 @@ impl OciInterface { |
586 | if let TagOrDigest::Tag(name) = tag_or_digest { |
587 | self.storage |
588 | .write_all( |
589 | - &Address::Tag { namespace, name }, |
590 | + &Address::Tag { |
591 | + namespace: namespace.clone(), |
592 | + name: name.clone(), |
593 | + }, |
594 | digest.to_string().as_bytes(), |
595 | ) |
596 | .await |
597 | @@ -212,12 +239,18 @@ impl OciInterface { |
598 | let blob_addr = Address::Blob { |
599 | digest: match tag_or_digest { |
600 | TagOrDigest::Tag(name) => { |
601 | - &self.resolve_link(&Address::Tag { namespace, name }).await? |
602 | + self.resolve_link(&Address::Tag { |
603 | + namespace: namespace.clone(), |
604 | + name: name.clone(), |
605 | + }) |
606 | + .await? |
607 | } |
608 | TagOrDigest::Digest(digest) => { |
609 | - &self |
610 | - .resolve_link(&Address::Reference { namespace, digest }) |
611 | - .await? |
612 | + self.resolve_link(&Address::Reference { |
613 | + namespace: namespace.clone(), |
614 | + digest: digest.clone(), |
615 | + }) |
616 | + .await? |
617 | } |
618 | }, |
619 | }; |
620 | @@ -232,7 +265,9 @@ impl OciInterface { |
621 | } |
622 | |
623 | pub async fn has_blob(&self, digest: &Digest) -> Result<bool, Error> { |
624 | - let blob_addr = Address::Blob { digest }; |
625 | + let blob_addr = Address::Blob { |
626 | + digest: digest.clone(), |
627 | + }; |
628 | self.storage |
629 | .exists(&blob_addr) |
630 | .await |
631 | @@ -247,12 +282,18 @@ impl OciInterface { |
632 | let blob_addr = Address::Blob { |
633 | digest: match tag_or_digest { |
634 | TagOrDigest::Tag(name) => { |
635 | - &self.resolve_link(&Address::Tag { namespace, name }).await? |
636 | + self.resolve_link(&Address::Tag { |
637 | + namespace: namespace.clone(), |
638 | + name: name.clone(), |
639 | + }) |
640 | + .await? |
641 | } |
642 | TagOrDigest::Digest(digest) => { |
643 | - &self |
644 | - .resolve_link(&Address::Reference { namespace, digest }) |
645 | - .await? |
646 | + self.resolve_link(&Address::Reference { |
647 | + namespace: namespace.clone(), |
648 | + digest: digest.clone(), |
649 | + }) |
650 | + .await? |
651 | } |
652 | }, |
653 | }; |
654 | @@ -263,7 +304,9 @@ impl OciInterface { |
655 | } |
656 | |
657 | pub async fn read_blob(&self, digest: &Digest) -> Result<InnerStream, Error> { |
658 | - let blob_addr = Address::Blob { digest }; |
659 | + let blob_addr = Address::Blob { |
660 | + digest: digest.clone(), |
661 | + }; |
662 | self.storage.read(&blob_addr).await.map_err(Error::Storage) |
663 | } |
664 | } |
665 | diff --git a/src/storage.rs b/src/storage.rs |
666 | index d808848..2c62c19 100644 |
667 | --- a/src/storage.rs |
668 | +++ b/src/storage.rs |
669 | @@ -46,18 +46,18 @@ pub trait StorageIface: Sync + Send { |
670 | // async fn stat(&self, addr: &Address) -> Result<Option<Object>, Error>; |
671 | // async fn read_bytes(&self, addr: &Address) -> Result<Option<Vec<u8>>, Error>; |
672 | /// Check if an object exists at the given address |
673 | - async fn exists<'a>(&self, path: &Address<'a>) -> Result<bool, Error>; |
674 | + async fn exists<'a>(&self, path: &Address) -> Result<bool, Error>; |
675 | /// Write bytes to the address, truncating any existing object |
676 | - async fn write_all<'a>(&self, path: &Address<'a>, bytes: &[u8]) -> Result<(), Error>; |
677 | + async fn write_all<'a>(&self, path: &Address, bytes: &[u8]) -> Result<(), Error>; |
678 | |
679 | /// write bytes to a file that has already been created |
680 | - async fn write<'a>(&self, path: &Address<'a>, bytes: &[u8]) -> Result<(), Error>; |
681 | + async fn write<'a>(&self, path: &Address, bytes: &[u8]) -> Result<(), Error>; |
682 | |
683 | - async fn mv<'a>(&self, src: &Address<'a>, dst: &Address<'a>) -> Result<(), Error>; |
684 | + async fn mv<'a>(&self, src: &Address, dst: &Address) -> Result<(), Error>; |
685 | // fn mv (&self, )std::future::Future<Output = ()> + Send |
686 | - async fn read<'a>(&self, src: &Address<'a>) -> Result<InnerStream, Error>; |
687 | - async fn read_bytes<'a>(&self, src: &Address<'a>) -> Result<Bytes, Error>; |
688 | - async fn delete<'a>(&self, src: &Address<'a>) -> Result<(), Error>; |
689 | + async fn read<'a>(&self, src: &Address) -> Result<InnerStream, Error>; |
690 | + async fn read_bytes<'a>(&self, src: &Address) -> Result<Bytes, Error>; |
691 | + async fn delete<'a>(&self, src: &Address) -> Result<(), Error>; |
692 | } |
693 | |
694 | #[derive(Debug, Clone)] |
695 | diff --git a/src/storage_fs.rs b/src/storage_fs.rs |
696 | index cf725f9..19895f2 100644 |
697 | --- a/src/storage_fs.rs |
698 | +++ b/src/storage_fs.rs |
699 | @@ -32,7 +32,7 @@ impl FileSystem { |
700 | |
701 | #[async_trait::async_trait] |
702 | impl StorageIface for FileSystem { |
703 | - async fn exists<'a>(&self, addr: &Address<'a>) -> Result<bool, Error> { |
704 | + async fn exists<'a>(&self, addr: &Address) -> Result<bool, Error> { |
705 | let path = addr.path(&self.base); |
706 | if tokio::fs::try_exists(&path) |
707 | .await |
708 | @@ -45,7 +45,7 @@ impl StorageIface for FileSystem { |
709 | } |
710 | } |
711 | |
712 | - async fn write_all<'a>(&self, addr: &Address<'a>, bytes: &[u8]) -> Result<(), Error> { |
713 | + async fn write_all<'a>(&self, addr: &Address, bytes: &[u8]) -> Result<(), Error> { |
714 | let path = addr.path(&self.base); |
715 | self.ensure_dir(&path).await?; |
716 | let mut fp = tokio::fs::OpenOptions::new() |
717 | @@ -61,7 +61,7 @@ impl StorageIface for FileSystem { |
718 | Ok(()) |
719 | } |
720 | |
721 | - async fn write<'a>(&self, addr: &Address<'a>, bytes: &[u8]) -> Result<(), Error> { |
722 | + async fn write<'a>(&self, addr: &Address, bytes: &[u8]) -> Result<(), Error> { |
723 | let path = addr.path(&self.base); |
724 | let mut fp = tokio::fs::OpenOptions::new() |
725 | .create(false) |
726 | @@ -76,7 +76,7 @@ impl StorageIface for FileSystem { |
727 | Ok(()) |
728 | } |
729 | |
730 | - async fn mv<'a>(&self, src: &Address<'a>, dst: &Address) -> Result<(), Error> { |
731 | + async fn mv<'a>(&self, src: &Address, dst: &Address) -> Result<(), Error> { |
732 | let src_path = src.path(&self.base); |
733 | let dst_path = dst.path(&self.base); |
734 | self.ensure_dir(&dst_path).await?; |
735 | @@ -85,7 +85,7 @@ impl StorageIface for FileSystem { |
736 | Ok(()) |
737 | } |
738 | |
739 | - async fn read<'a>(&self, src: &Address<'a>) -> Result<InnerStream, Error> { |
740 | + async fn read<'a>(&self, src: &Address) -> Result<InnerStream, Error> { |
741 | let path = src.path(&self.base); |
742 | let fp = tokio::fs::File::open(path.as_path()) |
743 | .await |
744 | @@ -97,7 +97,7 @@ impl StorageIface for FileSystem { |
745 | Ok(InnerStream::new(stream.boxed())) |
746 | } |
747 | |
748 | - async fn read_bytes<'a>(&self, src: &Address<'a>) -> Result<Bytes, Error> { |
749 | + async fn read_bytes<'a>(&self, src: &Address) -> Result<Bytes, Error> { |
750 | let path = src.path(&self.base); |
751 | let payload = tokio::fs::read(path.as_path()) |
752 | .await |
753 | @@ -108,7 +108,7 @@ impl StorageIface for FileSystem { |
754 | Ok(Bytes::from(payload)) |
755 | } |
756 | |
757 | - async fn delete<'a>(&self, src: &Address<'a>) -> Result<(), Error> { |
758 | + async fn delete<'a>(&self, src: &Address) -> Result<(), Error> { |
759 | let path = src.path(&self.base); |
760 | tokio::fs::remove_file(path.as_path()).await?; |
761 | Ok(()) |