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
100
#![deny(warnings)]
#![deny(clippy::all)]

use failure::{Error, ResultExt};
use futures::compat::*;
use futures::prelude::*;
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};

pub trait Frontend {
    type RunFuture: Future<Output = Result<FrontendOutput, Error>>;

    fn run(self, bridge: Bridge, options: Options) -> Self::RunFuture;
}

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(())
}