1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//! OCI Registry As Storage (ORAS) provides a mechanism for storing arbitrary
//! artifacts in registries that support the
//! [Open Container Initiative](https://opencontainers.org) distribution
//! specification.
//! 
//! More information about the ORAS project may be found at
//! [github.com/deislabs/oras](https://github.com/deislabs/oras).
//! 
//! This crate is not affiliated with deislabs.
#![deny(missing_docs)]
#![warn(rust_2018_idioms)]

use oci_distribution::{Reference, Client as OCIClient};
use oci_distribution::manifest::OciManifest;
use std::convert::{TryFrom, TryInto};

/// Connects to an OCI registry and pushes or pulls artifacts with custom metadata types.
#[derive(Default)]
pub struct Client {
    oci_client: OCIClient,
}

impl Client {
    /// Allocates a new `Client`.
    pub fn new(oci_client: OCIClient) -> Self {
        Self{
            oci_client,
        }
    }

    /// Push an object to an OCI registry.
    pub async fn push<T>(&mut self, _reference: &Reference, _object: T) -> anyhow::Result<OciManifest>
    where T: TryInto<std::vec::Vec<u8>, Error = anyhow::Error> + Send {
        unimplemented!()
    }

    /// Pull an object from an OCI registry.
    pub async fn pull<T>(&mut self, reference: &Reference) -> anyhow::Result<(T, OciManifest)>
    where T: TryFrom<std::vec::Vec<u8>, Error = anyhow::Error> + Send {
        let data = self.oci_client.pull_image(&reference).await?;

        let result = T::try_from(data.content)?;

        // TODO(jlegrone): set manifest fields instead of using default()
        let mut manifest = OciManifest::default();
        manifest.config.digest = data.digest.expect("digest should always be known after pull");

        Ok((result, manifest))
    }
}

#[cfg(test)]
mod tests {
    use oci_distribution::Reference;
    use std::convert::TryFrom;
    use std::vec::Vec;

    #[derive(Debug)]
    struct TestObject {
        content: String,
    }

    impl TryFrom<Vec<u8>> for TestObject {
        type Error = anyhow::Error;

        fn try_from(from: Vec<u8>) -> anyhow::Result<Self> {
            Ok(Self{
                content: std::str::from_utf8(&from)?.to_owned(),
            })
        }
    }

    #[tokio::test]
    async fn test_pull_image() {
        let test_ref = std::env::var("TEST_ORAS_REF").expect("TEST_ORAS_REF env var set");
        let image = Reference::try_from(test_ref).expect("failed to parse reference");

        let (result, desc) = crate::Client::default().pull::<TestObject>(&image).await.expect("failed to pull manifest");

        assert_eq!("sha256:f29dba55022eec8c0ce1cbfaaed45f2352ab3fbbb1cdcd5ea30ca3513deb70c9", desc.config.digest);
        assert_eq!(
            "{\"description\":\"\",\"invocationImages\":null,\"name\":\"\",\"schemaVersion\":\"\",\"version\":\"v1\"}".to_owned(),
            result.content,
        );
    }
}