buildkit_frontend/
bridge.rs

1use std::collections::HashMap;
2use std::path::PathBuf;
3use std::sync::Arc;
4
5use failure::{bail, format_err, Error, ResultExt};
6use log::*;
7use tokio::sync::Mutex;
8
9use tonic::transport::channel::Channel;
10use tonic::Request;
11
12use buildkit_proto::google::rpc::Status;
13use buildkit_proto::moby::buildkit::v1::frontend::llb_bridge_client::LlbBridgeClient;
14use buildkit_proto::moby::buildkit::v1::frontend::{
15    result::Result as RefResult, ReadFileRequest, ResolveImageConfigRequest, Result as Output,
16    ReturnRequest, SolveRequest,
17};
18
19pub use buildkit_llb::ops::source::{ImageSource, ResolveMode};
20pub use buildkit_llb::ops::Terminal;
21pub use buildkit_proto::moby::buildkit::v1::frontend::FileRange;
22
23use crate::error::ErrorCode;
24use crate::oci::ImageSpecification;
25use crate::options::common::CacheOptionsEntry;
26use crate::utils::OutputRef;
27
28#[derive(Clone)]
29pub struct Bridge {
30    client: Arc<Mutex<LlbBridgeClient<Channel>>>,
31}
32
33impl Bridge {
34    pub(crate) fn new(channel: Channel) -> Self {
35        Self {
36            client: Arc::new(Mutex::new(LlbBridgeClient::new(channel))),
37        }
38    }
39
40    pub async fn resolve_image_config(
41        &self,
42        image: &ImageSource,
43        log: Option<&str>,
44    ) -> Result<(String, ImageSpecification), Error> {
45        let request = ResolveImageConfigRequest {
46            r#ref: image.canonical_name(),
47            platform: None,
48            resolve_mode: image.resolve_mode().unwrap_or_default().to_string(),
49            log_name: log.unwrap_or_default().into(),
50        };
51
52        debug!("requesting to resolve an image: {:?}", request);
53        let response = {
54            self.client
55                .lock()
56                .await
57                .resolve_image_config(Request::new(request))
58                .await
59                .unwrap()
60                .into_inner()
61        };
62
63        Ok((
64            response.digest,
65            serde_json::from_slice(&response.config)
66                .context("Unable to parse image specification")?,
67        ))
68    }
69
70    pub async fn solve<'a, 'b: 'a>(&'a self, graph: Terminal<'b>) -> Result<OutputRef, Error> {
71        self.solve_with_cache(graph, &[]).await
72    }
73
74    pub async fn solve_with_cache<'a, 'b: 'a>(
75        &'a self,
76        graph: Terminal<'b>,
77        cache: &[CacheOptionsEntry],
78    ) -> Result<OutputRef, Error> {
79        debug!("serializing a graph to request");
80        let request = SolveRequest {
81            definition: Some(graph.into_definition()),
82            exporter_attr: vec![],
83            allow_result_return: true,
84            cache_imports: cache.iter().cloned().map(Into::into).collect(),
85
86            ..Default::default()
87        };
88
89        debug!("solving with cache from: {:?}", cache);
90        debug!("requesting to solve a graph");
91        let response = {
92            self.client
93                .lock()
94                .await
95                .solve(Request::new(request))
96                .await
97                .context("Unable to solve the graph")?
98                .into_inner()
99                .result
100                .ok_or_else(|| format_err!("Unable to extract solve result"))?
101        };
102
103        debug!("got response: {:#?}", response);
104
105        let inner = {
106            response
107                .result
108                .ok_or_else(|| format_err!("Unable to extract solve result"))?
109        };
110
111        match inner {
112            RefResult::Ref(inner) => Ok(OutputRef(inner)),
113            other => bail!("Unexpected solve response: {:?}", other),
114        }
115    }
116
117    pub async fn read_file<'a, 'b: 'a, P>(
118        &'a self,
119        layer: &'b OutputRef,
120        path: P,
121        range: Option<FileRange>,
122    ) -> Result<Vec<u8>, Error>
123    where
124        P: Into<PathBuf>,
125    {
126        let file_path = path.into().display().to_string();
127        debug!("requesting a file contents: {:#?}", file_path);
128
129        let request = ReadFileRequest {
130            r#ref: layer.0.clone(),
131            file_path,
132            range,
133        };
134
135        let response = {
136            self.client
137                .lock()
138                .await
139                .read_file(Request::new(request))
140                .await
141                .context("Unable to read the file")?
142                .into_inner()
143                .data
144        };
145
146        Ok(response)
147    }
148
149    pub(crate) async fn finish_with_success(
150        self,
151        output: OutputRef,
152        config: Option<ImageSpecification>,
153    ) -> Result<(), Error> {
154        let mut metadata = HashMap::new();
155
156        if let Some(config) = config {
157            metadata.insert("containerimage.config".into(), serde_json::to_vec(&config)?);
158        }
159
160        let request = ReturnRequest {
161            error: None,
162            result: Some(Output {
163                result: Some(RefResult::Ref(output.0)),
164                metadata,
165            }),
166        };
167
168        self.client
169            .lock()
170            .await
171            .r#return(Request::new(request))
172            .await?;
173
174        // TODO: gracefully shutdown the HTTP/2 connection
175
176        Ok(())
177    }
178
179    pub(crate) async fn finish_with_error<S>(self, code: ErrorCode, message: S) -> Result<(), Error>
180    where
181        S: Into<String>,
182    {
183        let request = ReturnRequest {
184            result: None,
185            error: Some(Status {
186                code: code as i32,
187                message: message.into(),
188                details: vec![],
189            }),
190        };
191
192        debug!("sending an error result: {:#?}", request);
193        self.client
194            .lock()
195            .await
196            .r#return(Request::new(request))
197            .await?;
198
199        // TODO: gracefully shutdown the HTTP/2 connection
200
201        Ok(())
202    }
203}