tinkc 0.1.6

Tinkerbell tink grpc client wrapper
Documentation
use derive_builder::Builder;
use eyre::{eyre, Result};
pub mod grpc;
pub use grpc::hardware::{self, GetRequest as HardwareGetRequest, Hardware, HardwareServiceClient};
pub use grpc::workflow::{
    self, GetRequest as WorkflowGetRequest, Workflow, WorkflowActionStatus, WorkflowServiceClient,
};
use tonic::transport::{Certificate, Channel, ClientTlsConfig};

#[derive(Clone)]
pub struct Tink {
    workflow_client: WorkflowServiceClient<Channel>,
    hardware_client: HardwareServiceClient<Channel>,
}

#[derive(Clone)]
pub enum TinkCert<'a> {
    File(&'a str),
    Str(&'a str),
    Vec(Vec<u8>),
}

#[derive(Builder)]
pub struct TinkConfig<'a> {
    endpoint: &'a str,
    domain: &'a str,
    cert: TinkCert<'a>,
}

impl Tink {
    pub async fn new(config: TinkConfig<'_>) -> Result<Self> {
        let pem = match config.cert {
            TinkCert::File(cert) => tokio::fs::read(cert).await?,
            TinkCert::Str(cert) => cert.as_bytes().to_vec(),
            TinkCert::Vec(cert) => cert,
        };
        let ca = Certificate::from_pem(pem);

        let tls = ClientTlsConfig::new()
            .ca_certificate(ca)
            .domain_name(config.domain);

        let channel = Channel::from_shared(config.endpoint.to_owned())?
            .tls_config(tls)?
            .connect()
            .await?;

        Ok(Self {
            workflow_client: WorkflowServiceClient::new(channel.clone()),
            hardware_client: HardwareServiceClient::new(channel),
        })
    }

    pub async fn workflows<T>(&mut self) -> Result<Vec<T>>
    where
        T: TryFrom<Workflow>,
    {
        let mut workflows = self
            .workflow_client
            .list_workflows(workflow::Empty {})
            .await?
            .into_inner();
        let mut resolved = Vec::<T>::new();
        while let Some(workflow) = workflows.message().await? {
            if let Ok(workflow) = workflow.try_into() {
                resolved.push(workflow);
            }
        }
        Ok(resolved)
    }

    pub async fn workflow_events<T>(&mut self, id: String) -> Result<Vec<T>>
    where
        T: TryFrom<WorkflowActionStatus>,
    {
        let mut stats = self
            .workflow_client
            .show_workflow_events(WorkflowGetRequest { id })
            .await?
            .into_inner();
        let mut resolved = Vec::<T>::new();
        while let Some(status) = stats.message().await? {
            if let Ok(status) = status.try_into() {
                resolved.push(status)
            }
        }
        Ok(resolved)
    }

    pub async fn hardware<T>(&mut self) -> Result<Vec<T>>
    where
        T: TryFrom<Hardware>,
    {
        let mut hardware = self
            .hardware_client
            .all(hardware::Empty {})
            .await?
            .into_inner();
        let mut resolved = Vec::<T>::new();
        while let Some(hardware) = hardware.message().await? {
            if let Ok(hardware) = hardware.try_into() {
                resolved.push(hardware);
            }
        }
        Ok(resolved)
    }

    pub async fn hardware_from_mac<T>(&mut self, mac: String) -> Result<T>
    where
        T: TryFrom<Hardware>,
    {
        let response = self
            .hardware_client
            .by_mac(HardwareGetRequest {
                mac,
                id: "".to_string(),
                ip: "".to_string(),
            })
            .await?;
        response
            .into_inner()
            .try_into()
            .map_err(|_| eyre!("Hardware From MAC Failed"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use eyre::Result;

    #[test]
    fn happy_tink_config_builder_file() -> Result<()> {
        TinkConfigBuilder::default()
            .cert(TinkCert::File("ca.crt"))
            .domain("example.com")
            .endpoint("localhost:9999")
            .build()?;
        Ok(())
    }

    #[test]
    fn happy_tink_config_builder_string() -> Result<()> {
        TinkConfigBuilder::default()
            .cert(TinkCert::Str("some content"))
            .domain("example.com")
            .endpoint("localhost:9999")
            .build()?;
        Ok(())
    }
}