buildkit_llb/ops/fs/
mod.rs

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
21/// Umbrella operation that handles file system related routines.
22/// Dockerfile's `COPY` directive is a partial case of this.
23pub 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            // TODO: improve the correct, but inefficent serialization
211            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}