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
87
88
89
90
91
92
93
94
95
96
97
98
99
#![deny(warnings)]
#![deny(clippy::all)]

use async_trait::async_trait;
use failure::{Error, ResultExt};
use futures::compat::*;
use hyper::client::connect::Destination;
use log::*;
use tower_hyper::{client, util};
use tower_util::MakeService;

mod bridge;
mod error;
mod options;
mod stdio;
mod utils;

pub mod oci;

use oci::ImageSpecification;

pub use self::bridge::Bridge;
pub use self::error::ErrorCode;
pub use self::options::Options;
pub use self::stdio::{StdioConnector, StdioSocket};
pub use self::utils::{ErrorWithCauses, OutputRef};

#[async_trait]
pub trait Frontend {
    async fn run(self, bridge: Bridge, options: Options) -> Result<FrontendOutput, Error>;
}

pub struct FrontendOutput {
    output: OutputRef,
    image_spec: Option<ImageSpecification>,
}

impl FrontendOutput {
    pub fn with_ref(output: OutputRef) -> Self {
        Self {
            output,
            image_spec: None,
        }
    }

    pub fn with_spec_and_ref(spec: ImageSpecification, output: OutputRef) -> Self {
        Self {
            output,
            image_spec: Some(spec),
        }
    }
}

pub async fn run_frontend<F: Frontend>(frontend: F) -> Result<(), Error> {
    let connector = util::Connector::new(StdioConnector);
    let settings = client::Builder::new().http2_only(true).clone();

    let mut make_client = client::Connect::with_builder(connector, settings);
    let fake_destination = Destination::try_from_uri("http://localhost".parse()?)?;

    let connection = {
        tower_request_modifier::Builder::new()
            .set_origin("http://localhost")
            .build(make_client.make_service(fake_destination).compat().await?)
            .unwrap()
    };

    let bridge = Bridge::new(connection);
    let options = Options::analyse();

    debug!("running a frontend entrypoint");
    match frontend.run(bridge.clone(), options).await {
        Ok(output) => {
            bridge
                .finish_with_success(output.output, output.image_spec)
                .await
                .context("Unable to send a success result")?;
        }

        Err(error) => {
            let error = ErrorWithCauses::multi_line(error);

            error!("Frontend entrypoint failed: {}", error);

            // https://godoc.org/google.golang.org/grpc/codes#Code
            bridge
                .finish_with_error(
                    ErrorCode::Unknown,
                    ErrorWithCauses::single_line(error.into_inner()).to_string(),
                )
                .await
                .context("Unable to send an error result")?;
        }
    }

    // TODO: gracefully shutdown the HTTP/2 connection

    Ok(())
}