buildkit_frontend/
bridge.rs1use 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 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 Ok(())
202 }
203}