buildkit_frontend/
lib.rs

1#![deny(warnings)]
2#![deny(clippy::all)]
3
4use failure::{Error, ResultExt};
5use log::*;
6use serde::de::DeserializeOwned;
7use tonic::transport::Endpoint;
8use tower::service_fn;
9
10mod bridge;
11mod error;
12mod stdio;
13mod utils;
14
15pub mod oci;
16pub mod options;
17
18use oci::ImageSpecification;
19
20pub use self::bridge::Bridge;
21pub use self::error::ErrorCode;
22pub use self::options::Options;
23pub use self::stdio::{stdio_connector, StdioSocket};
24pub use self::utils::{ErrorWithCauses, OutputRef};
25
26#[tonic::async_trait]
27pub trait Frontend<O = Options>
28where
29    O: DeserializeOwned,
30{
31    async fn run(self, bridge: Bridge, options: O) -> Result<FrontendOutput, Error>;
32}
33
34pub struct FrontendOutput {
35    output: OutputRef,
36    image_spec: Option<ImageSpecification>,
37}
38
39impl FrontendOutput {
40    pub fn with_ref(output: OutputRef) -> Self {
41        Self {
42            output,
43            image_spec: None,
44        }
45    }
46
47    pub fn with_spec_and_ref(spec: ImageSpecification, output: OutputRef) -> Self {
48        Self {
49            output,
50            image_spec: Some(spec),
51        }
52    }
53}
54
55pub async fn run_frontend<F, O>(frontend: F) -> Result<(), Error>
56where
57    F: Frontend<O>,
58    O: DeserializeOwned,
59{
60    let channel = {
61        Endpoint::from_static("http://[::]:50051")
62            .connect_with_connector(service_fn(stdio_connector))
63            .await?
64    };
65
66    let bridge = Bridge::new(channel);
67
68    match frontend_entrypoint(&bridge, frontend).await {
69        Ok(output) => {
70            bridge
71                .finish_with_success(output.output, output.image_spec)
72                .await
73                .context("Unable to send a success result")?;
74        }
75
76        Err(error) => {
77            let error = ErrorWithCauses::multi_line(error);
78
79            error!("Frontend entrypoint failed: {}", error);
80
81            // https://godoc.org/google.golang.org/grpc/codes#Code
82            bridge
83                .finish_with_error(
84                    ErrorCode::Unknown,
85                    ErrorWithCauses::single_line(error.into_inner()).to_string(),
86                )
87                .await
88                .context("Unable to send an error result")?;
89        }
90    }
91
92    // TODO: gracefully shutdown the HTTP/2 connection
93
94    Ok(())
95}
96
97async fn frontend_entrypoint<F, O>(bridge: &Bridge, frontend: F) -> Result<FrontendOutput, Error>
98where
99    F: Frontend<O>,
100    O: DeserializeOwned,
101{
102    let options = options::from_env(std::env::vars()).context("Unable to parse options")?;
103
104    debug!("running a frontend entrypoint");
105    frontend.run(bridge.clone(), options).await
106}