1use std::fmt::Debug;
2use std::path::Path;
3
4use buildkit_proto::pb;
5
6use crate::serialization::{Context, Result};
7use crate::utils::OutputIdx;
8
9mod copy;
10mod mkdir;
11mod mkfile;
12mod path;
13mod sequence;
14
15pub use self::copy::CopyOperation;
16pub use self::mkdir::MakeDirOperation;
17pub use self::mkfile::MakeFileOperation;
18pub use self::path::{LayerPath, UnsetPath};
19pub use self::sequence::SequenceOperation;
20
21pub struct FileSystem;
24
25impl FileSystem {
26 pub fn sequence() -> SequenceOperation<'static> {
27 SequenceOperation::new()
28 }
29
30 pub fn copy() -> copy::CopyOperation<UnsetPath, UnsetPath> {
31 CopyOperation::new()
32 }
33
34 pub fn mkdir<P>(output: OutputIdx, layer: LayerPath<P>) -> MakeDirOperation
35 where
36 P: AsRef<Path>,
37 {
38 MakeDirOperation::new(output, layer)
39 }
40
41 pub fn mkfile<P>(output: OutputIdx, layer: LayerPath<P>) -> MakeFileOperation
42 where
43 P: AsRef<Path>,
44 {
45 MakeFileOperation::new(output, layer)
46 }
47}
48
49pub trait FileOperation: Debug + Send + Sync {
50 fn output(&self) -> i32;
51
52 fn serialize_inputs(&self, cx: &mut Context) -> Result<Vec<pb::Input>>;
53 fn serialize_action(&self, inputs_count: usize, inputs_offset: usize)
54 -> Result<pb::FileAction>;
55}
56
57#[test]
58fn copy_serialization() {
59 use crate::prelude::*;
60 use buildkit_proto::pb::{file_action::Action, op::Op, FileAction, FileActionCopy, FileOp};
61
62 let context = Source::local("context");
63 let builder_image = Source::image("rustlang/rust:nightly");
64
65 let operation = FileSystem::sequence()
66 .append(
67 FileSystem::copy()
68 .from(LayerPath::Other(context.output(), "Cargo.toml"))
69 .to(OutputIdx(0), LayerPath::Scratch("Cargo.toml")),
70 )
71 .append(
72 FileSystem::copy()
73 .from(LayerPath::Other(builder_image.output(), "/bin/sh"))
74 .to(OutputIdx(1), LayerPath::Own(OwnOutputIdx(0), "/bin/sh")),
75 )
76 .append(
77 FileSystem::copy()
78 .from(LayerPath::Own(OwnOutputIdx(1), "Cargo.toml"))
79 .to(OutputIdx(2), LayerPath::Scratch("Cargo.toml")),
80 );
81
82 crate::check_op!(
83 operation,
84 |digest| { "sha256:c4f7fb723fa87f03788aaf660dc9110ad8748fc9971e13713f103b632c05ae96" },
85 |description| { vec![] },
86 |caps| { vec!["file.base"] },
87 |cached_tail| {
88 vec![
89 "sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
90 "sha256:dee2a3d7dd482dd8098ba543ff1dcb01efd29fcd16fdb0979ef556f38564543a",
91 ]
92 },
93 |inputs| {
94 vec![
95 (
96 "sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
97 0,
98 ),
99 (
100 "sha256:dee2a3d7dd482dd8098ba543ff1dcb01efd29fcd16fdb0979ef556f38564543a",
101 0,
102 ),
103 ]
104 },
105 |op| {
106 Op::File(FileOp {
107 actions: vec![
108 FileAction {
109 input: -1,
110 secondary_input: 0,
111 output: 0,
112 action: Some(Action::Copy(FileActionCopy {
113 src: "Cargo.toml".into(),
114 dest: "Cargo.toml".into(),
115 owner: None,
116 mode: -1,
117 follow_symlink: false,
118 dir_copy_contents: false,
119 attempt_unpack_docker_compatibility: false,
120 create_dest_path: false,
121 allow_wildcard: false,
122 allow_empty_wildcard: false,
123 timestamp: -1,
124 })),
125 },
126 FileAction {
127 input: 2,
128 secondary_input: 1,
129 output: 1,
130 action: Some(Action::Copy(FileActionCopy {
131 src: "/bin/sh".into(),
132 dest: "/bin/sh".into(),
133 owner: None,
134 mode: -1,
135 follow_symlink: false,
136 dir_copy_contents: false,
137 attempt_unpack_docker_compatibility: false,
138 create_dest_path: false,
139 allow_wildcard: false,
140 allow_empty_wildcard: false,
141 timestamp: -1,
142 })),
143 },
144 FileAction {
145 input: -1,
146 secondary_input: 3,
147 output: 2,
148 action: Some(Action::Copy(FileActionCopy {
149 src: "Cargo.toml".into(),
150 dest: "Cargo.toml".into(),
151 owner: None,
152 mode: -1,
153 follow_symlink: false,
154 dir_copy_contents: false,
155 attempt_unpack_docker_compatibility: false,
156 create_dest_path: false,
157 allow_wildcard: false,
158 allow_empty_wildcard: false,
159 timestamp: -1,
160 })),
161 },
162 ],
163 })
164 },
165 );
166}
167
168#[test]
169fn copy_with_params_serialization() {
170 use crate::prelude::*;
171 use buildkit_proto::pb::{file_action::Action, op::Op, FileAction, FileActionCopy, FileOp};
172
173 let context = Source::local("context");
174
175 let operation = FileSystem::sequence()
176 .append(
177 FileSystem::copy()
178 .from(LayerPath::Other(context.output(), "Cargo.toml"))
179 .to(OutputIdx(0), LayerPath::Scratch("Cargo.toml"))
180 .follow_symlinks(true),
181 )
182 .append(
183 FileSystem::copy()
184 .from(LayerPath::Other(context.output(), "Cargo.toml"))
185 .to(OutputIdx(1), LayerPath::Scratch("Cargo.toml"))
186 .recursive(true),
187 )
188 .append(
189 FileSystem::copy()
190 .from(LayerPath::Other(context.output(), "Cargo.toml"))
191 .to(OutputIdx(2), LayerPath::Scratch("Cargo.toml"))
192 .create_path(true),
193 )
194 .append(
195 FileSystem::copy()
196 .from(LayerPath::Other(context.output(), "Cargo.toml"))
197 .to(OutputIdx(3), LayerPath::Scratch("Cargo.toml"))
198 .wildcard(true),
199 );
200
201 crate::check_op!(
202 operation,
203 |digest| { "sha256:8be9c1c8335d53c894d0f5848ef354c69a96a469a72b00aadae704b23d465022" },
204 |description| { vec![] },
205 |caps| { vec!["file.base"] },
206 |cached_tail| {
207 vec!["sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702"]
208 },
209 |inputs| {
210 vec![
212 (
213 "sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
214 0,
215 ),
216 (
217 "sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
218 0,
219 ),
220 (
221 "sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
222 0,
223 ),
224 (
225 "sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
226 0,
227 ),
228 ]
229 },
230 |op| {
231 Op::File(FileOp {
232 actions: vec![
233 FileAction {
234 input: -1,
235 secondary_input: 0,
236 output: 0,
237 action: Some(Action::Copy(FileActionCopy {
238 src: "Cargo.toml".into(),
239 dest: "Cargo.toml".into(),
240 owner: None,
241 mode: -1,
242 follow_symlink: true,
243 dir_copy_contents: false,
244 attempt_unpack_docker_compatibility: false,
245 create_dest_path: false,
246 allow_wildcard: false,
247 allow_empty_wildcard: false,
248 timestamp: -1,
249 })),
250 },
251 FileAction {
252 input: -1,
253 secondary_input: 1,
254 output: 1,
255 action: Some(Action::Copy(FileActionCopy {
256 src: "Cargo.toml".into(),
257 dest: "Cargo.toml".into(),
258 owner: None,
259 mode: -1,
260 follow_symlink: false,
261 dir_copy_contents: true,
262 attempt_unpack_docker_compatibility: false,
263 create_dest_path: false,
264 allow_wildcard: false,
265 allow_empty_wildcard: false,
266 timestamp: -1,
267 })),
268 },
269 FileAction {
270 input: -1,
271 secondary_input: 2,
272 output: 2,
273 action: Some(Action::Copy(FileActionCopy {
274 src: "Cargo.toml".into(),
275 dest: "Cargo.toml".into(),
276 owner: None,
277 mode: -1,
278 follow_symlink: false,
279 dir_copy_contents: false,
280 attempt_unpack_docker_compatibility: false,
281 create_dest_path: true,
282 allow_wildcard: false,
283 allow_empty_wildcard: false,
284 timestamp: -1,
285 })),
286 },
287 FileAction {
288 input: -1,
289 secondary_input: 3,
290 output: 3,
291 action: Some(Action::Copy(FileActionCopy {
292 src: "Cargo.toml".into(),
293 dest: "Cargo.toml".into(),
294 owner: None,
295 mode: -1,
296 follow_symlink: false,
297 dir_copy_contents: false,
298 attempt_unpack_docker_compatibility: false,
299 create_dest_path: false,
300 allow_wildcard: true,
301 allow_empty_wildcard: false,
302 timestamp: -1,
303 })),
304 },
305 ],
306 })
307 },
308 );
309}
310
311#[test]
312fn mkdir_serialization() {
313 use crate::prelude::*;
314 use buildkit_proto::pb::{file_action::Action, op::Op, FileAction, FileActionMkDir, FileOp};
315
316 let context = Source::local("context");
317
318 let operation = FileSystem::sequence()
319 .append(
320 FileSystem::mkdir(
321 OutputIdx(0),
322 LayerPath::Other(context.output(), "/new-crate"),
323 )
324 .make_parents(true),
325 )
326 .append(FileSystem::mkdir(
327 OutputIdx(1),
328 LayerPath::Scratch("/new-crate"),
329 ))
330 .append(FileSystem::mkdir(
331 OutputIdx(2),
332 LayerPath::Own(OwnOutputIdx(1), "/another-crate/deep/directory"),
333 ));
334
335 crate::check_op!(
336 operation,
337 |digest| { "sha256:bfcd58256cba441c6d9e89c439bc6640b437d47213472cf8491646af4f0aa5b2" },
338 |description| { vec![] },
339 |caps| { vec!["file.base"] },
340 |cached_tail| {
341 vec!["sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702"]
342 },
343 |inputs| {
344 vec![(
345 "sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
346 0,
347 )]
348 },
349 |op| {
350 Op::File(FileOp {
351 actions: vec![
352 FileAction {
353 input: 0,
354 secondary_input: -1,
355 output: 0,
356 action: Some(Action::Mkdir(FileActionMkDir {
357 path: "/new-crate".into(),
358 owner: None,
359 mode: -1,
360 timestamp: -1,
361 make_parents: true,
362 })),
363 },
364 FileAction {
365 input: -1,
366 secondary_input: -1,
367 output: 1,
368 action: Some(Action::Mkdir(FileActionMkDir {
369 path: "/new-crate".into(),
370 owner: None,
371 mode: -1,
372 timestamp: -1,
373 make_parents: false,
374 })),
375 },
376 FileAction {
377 input: 2,
378 secondary_input: -1,
379 output: 2,
380 action: Some(Action::Mkdir(FileActionMkDir {
381 path: "/another-crate/deep/directory".into(),
382 owner: None,
383 mode: -1,
384 timestamp: -1,
385 make_parents: false,
386 })),
387 },
388 ],
389 })
390 },
391 );
392}
393
394#[test]
395fn mkfile_serialization() {
396 use crate::prelude::*;
397 use buildkit_proto::pb::{file_action::Action, op::Op, FileAction, FileActionMkFile, FileOp};
398
399 let context = Source::local("context");
400
401 let operation = FileSystem::sequence()
402 .append(
403 FileSystem::mkfile(
404 OutputIdx(0),
405 LayerPath::Other(context.output(), "/build-plan.json"),
406 )
407 .data(b"any bytes".to_vec()),
408 )
409 .append(FileSystem::mkfile(
410 OutputIdx(1),
411 LayerPath::Scratch("/build-graph.json"),
412 ))
413 .append(FileSystem::mkfile(
414 OutputIdx(2),
415 LayerPath::Own(OwnOutputIdx(1), "/llb.pb"),
416 ));
417
418 crate::check_op!(
419 operation,
420 |digest| { "sha256:9c0d9f741dfc9b4ea8d909ebf388bc354da0ee401eddf5633e8e4ece7e87d22d" },
421 |description| { vec![] },
422 |caps| { vec!["file.base"] },
423 |cached_tail| {
424 vec!["sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702"]
425 },
426 |inputs| {
427 vec![(
428 "sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
429 0,
430 )]
431 },
432 |op| {
433 Op::File(FileOp {
434 actions: vec![
435 FileAction {
436 input: 0,
437 secondary_input: -1,
438 output: 0,
439 action: Some(Action::Mkfile(FileActionMkFile {
440 path: "/build-plan.json".into(),
441 owner: None,
442 mode: -1,
443 timestamp: -1,
444 data: b"any bytes".to_vec(),
445 })),
446 },
447 FileAction {
448 input: -1,
449 secondary_input: -1,
450 output: 1,
451 action: Some(Action::Mkfile(FileActionMkFile {
452 path: "/build-graph.json".into(),
453 owner: None,
454 mode: -1,
455 timestamp: -1,
456 data: vec![],
457 })),
458 },
459 FileAction {
460 input: 2,
461 secondary_input: -1,
462 output: 2,
463 action: Some(Action::Mkfile(FileActionMkFile {
464 path: "/llb.pb".into(),
465 owner: None,
466 mode: -1,
467 timestamp: -1,
468 data: vec![],
469 })),
470 },
471 ],
472 })
473 },
474 );
475}