You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

476 lines
17 KiB
Rust

use std::fmt::Debug;
use std::path::Path;
use buildkit_proto::pb;
use crate::serialization::{Context, Result};
use crate::utils::OutputIdx;
mod copy;
mod mkdir;
mod mkfile;
mod path;
mod sequence;
pub use self::copy::CopyOperation;
pub use self::mkdir::MakeDirOperation;
pub use self::mkfile::MakeFileOperation;
pub use self::path::{LayerPath, UnsetPath};
pub use self::sequence::SequenceOperation;
/// Umbrella operation that handles file system related routines.
/// Dockerfile's `COPY` directive is a partial case of this.
pub struct FileSystem;
impl FileSystem {
pub fn sequence() -> SequenceOperation<'static> {
SequenceOperation::new()
}
pub fn copy() -> copy::CopyOperation<UnsetPath, UnsetPath> {
CopyOperation::new()
}
pub fn mkdir<P>(output: OutputIdx, layer: LayerPath<P>) -> MakeDirOperation
where
P: AsRef<Path>,
{
MakeDirOperation::new(output, layer)
}
pub fn mkfile<P>(output: OutputIdx, layer: LayerPath<P>) -> MakeFileOperation
where
P: AsRef<Path>,
{
MakeFileOperation::new(output, layer)
}
}
pub trait FileOperation: Debug + Send + Sync {
fn output(&self) -> i32;
fn serialize_inputs(&self, cx: &mut Context) -> Result<Vec<pb::Input>>;
fn serialize_action(&self, inputs_count: usize, inputs_offset: usize)
-> Result<pb::FileAction>;
}
#[test]
fn copy_serialization() {
use crate::prelude::*;
use buildkit_proto::pb::{file_action::Action, op::Op, FileAction, FileActionCopy, FileOp};
let context = Source::local("context");
let builder_image = Source::image("rustlang/rust:nightly");
let operation = FileSystem::sequence()
.append(
FileSystem::copy()
.from(LayerPath::Other(context.output(), "Cargo.toml"))
.to(OutputIdx(0), LayerPath::Scratch("Cargo.toml")),
)
.append(
FileSystem::copy()
.from(LayerPath::Other(builder_image.output(), "/bin/sh"))
.to(OutputIdx(1), LayerPath::Own(OwnOutputIdx(0), "/bin/sh")),
)
.append(
FileSystem::copy()
.from(LayerPath::Own(OwnOutputIdx(1), "Cargo.toml"))
.to(OutputIdx(2), LayerPath::Scratch("Cargo.toml")),
);
crate::check_op!(
operation,
|digest| { "sha256:c4f7fb723fa87f03788aaf660dc9110ad8748fc9971e13713f103b632c05ae96" },
|description| { vec![] },
|caps| { vec!["file.base"] },
|cached_tail| {
vec![
"sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
"sha256:dee2a3d7dd482dd8098ba543ff1dcb01efd29fcd16fdb0979ef556f38564543a",
]
},
|inputs| {
vec![
(
"sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
0,
),
(
"sha256:dee2a3d7dd482dd8098ba543ff1dcb01efd29fcd16fdb0979ef556f38564543a",
0,
),
]
},
|op| {
Op::File(FileOp {
actions: vec![
FileAction {
input: -1,
secondary_input: 0,
output: 0,
action: Some(Action::Copy(FileActionCopy {
src: "Cargo.toml".into(),
dest: "Cargo.toml".into(),
owner: None,
mode: -1,
follow_symlink: false,
dir_copy_contents: false,
attempt_unpack_docker_compatibility: false,
create_dest_path: false,
allow_wildcard: false,
allow_empty_wildcard: false,
timestamp: -1,
})),
},
FileAction {
input: 2,
secondary_input: 1,
output: 1,
action: Some(Action::Copy(FileActionCopy {
src: "/bin/sh".into(),
dest: "/bin/sh".into(),
owner: None,
mode: -1,
follow_symlink: false,
dir_copy_contents: false,
attempt_unpack_docker_compatibility: false,
create_dest_path: false,
allow_wildcard: false,
allow_empty_wildcard: false,
timestamp: -1,
})),
},
FileAction {
input: -1,
secondary_input: 3,
output: 2,
action: Some(Action::Copy(FileActionCopy {
src: "Cargo.toml".into(),
dest: "Cargo.toml".into(),
owner: None,
mode: -1,
follow_symlink: false,
dir_copy_contents: false,
attempt_unpack_docker_compatibility: false,
create_dest_path: false,
allow_wildcard: false,
allow_empty_wildcard: false,
timestamp: -1,
})),
},
],
})
},
);
}
#[test]
fn copy_with_params_serialization() {
use crate::prelude::*;
use buildkit_proto::pb::{file_action::Action, op::Op, FileAction, FileActionCopy, FileOp};
let context = Source::local("context");
let operation = FileSystem::sequence()
.append(
FileSystem::copy()
.from(LayerPath::Other(context.output(), "Cargo.toml"))
.to(OutputIdx(0), LayerPath::Scratch("Cargo.toml"))
.follow_symlinks(true),
)
.append(
FileSystem::copy()
.from(LayerPath::Other(context.output(), "Cargo.toml"))
.to(OutputIdx(1), LayerPath::Scratch("Cargo.toml"))
.recursive(true),
)
.append(
FileSystem::copy()
.from(LayerPath::Other(context.output(), "Cargo.toml"))
.to(OutputIdx(2), LayerPath::Scratch("Cargo.toml"))
.create_path(true),
)
.append(
FileSystem::copy()
.from(LayerPath::Other(context.output(), "Cargo.toml"))
.to(OutputIdx(3), LayerPath::Scratch("Cargo.toml"))
.wildcard(true),
);
crate::check_op!(
operation,
|digest| { "sha256:8be9c1c8335d53c894d0f5848ef354c69a96a469a72b00aadae704b23d465022" },
|description| { vec![] },
|caps| { vec!["file.base"] },
|cached_tail| {
vec!["sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702"]
},
|inputs| {
// TODO: improve the correct, but inefficent serialization
vec![
(
"sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
0,
),
(
"sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
0,
),
(
"sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
0,
),
(
"sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
0,
),
]
},
|op| {
Op::File(FileOp {
actions: vec![
FileAction {
input: -1,
secondary_input: 0,
output: 0,
action: Some(Action::Copy(FileActionCopy {
src: "Cargo.toml".into(),
dest: "Cargo.toml".into(),
owner: None,
mode: -1,
follow_symlink: true,
dir_copy_contents: false,
attempt_unpack_docker_compatibility: false,
create_dest_path: false,
allow_wildcard: false,
allow_empty_wildcard: false,
timestamp: -1,
})),
},
FileAction {
input: -1,
secondary_input: 1,
output: 1,
action: Some(Action::Copy(FileActionCopy {
src: "Cargo.toml".into(),
dest: "Cargo.toml".into(),
owner: None,
mode: -1,
follow_symlink: false,
dir_copy_contents: true,
attempt_unpack_docker_compatibility: false,
create_dest_path: false,
allow_wildcard: false,
allow_empty_wildcard: false,
timestamp: -1,
})),
},
FileAction {
input: -1,
secondary_input: 2,
output: 2,
action: Some(Action::Copy(FileActionCopy {
src: "Cargo.toml".into(),
dest: "Cargo.toml".into(),
owner: None,
mode: -1,
follow_symlink: false,
dir_copy_contents: false,
attempt_unpack_docker_compatibility: false,
create_dest_path: true,
allow_wildcard: false,
allow_empty_wildcard: false,
timestamp: -1,
})),
},
FileAction {
input: -1,
secondary_input: 3,
output: 3,
action: Some(Action::Copy(FileActionCopy {
src: "Cargo.toml".into(),
dest: "Cargo.toml".into(),
owner: None,
mode: -1,
follow_symlink: false,
dir_copy_contents: false,
attempt_unpack_docker_compatibility: false,
create_dest_path: false,
allow_wildcard: true,
allow_empty_wildcard: false,
timestamp: -1,
})),
},
],
})
},
);
}
#[test]
fn mkdir_serialization() {
use crate::prelude::*;
use buildkit_proto::pb::{file_action::Action, op::Op, FileAction, FileActionMkDir, FileOp};
let context = Source::local("context");
let operation = FileSystem::sequence()
.append(
FileSystem::mkdir(
OutputIdx(0),
LayerPath::Other(context.output(), "/new-crate"),
)
.make_parents(true),
)
.append(FileSystem::mkdir(
OutputIdx(1),
LayerPath::Scratch("/new-crate"),
))
.append(FileSystem::mkdir(
OutputIdx(2),
LayerPath::Own(OwnOutputIdx(1), "/another-crate/deep/directory"),
));
crate::check_op!(
operation,
|digest| { "sha256:bfcd58256cba441c6d9e89c439bc6640b437d47213472cf8491646af4f0aa5b2" },
|description| { vec![] },
|caps| { vec!["file.base"] },
|cached_tail| {
vec!["sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702"]
},
|inputs| {
vec![(
"sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
0,
)]
},
|op| {
Op::File(FileOp {
actions: vec![
FileAction {
input: 0,
secondary_input: -1,
output: 0,
action: Some(Action::Mkdir(FileActionMkDir {
path: "/new-crate".into(),
owner: None,
mode: -1,
timestamp: -1,
make_parents: true,
})),
},
FileAction {
input: -1,
secondary_input: -1,
output: 1,
action: Some(Action::Mkdir(FileActionMkDir {
path: "/new-crate".into(),
owner: None,
mode: -1,
timestamp: -1,
make_parents: false,
})),
},
FileAction {
input: 2,
secondary_input: -1,
output: 2,
action: Some(Action::Mkdir(FileActionMkDir {
path: "/another-crate/deep/directory".into(),
owner: None,
mode: -1,
timestamp: -1,
make_parents: false,
})),
},
],
})
},
);
}
#[test]
fn mkfile_serialization() {
use crate::prelude::*;
use buildkit_proto::pb::{file_action::Action, op::Op, FileAction, FileActionMkFile, FileOp};
let context = Source::local("context");
let operation = FileSystem::sequence()
.append(
FileSystem::mkfile(
OutputIdx(0),
LayerPath::Other(context.output(), "/build-plan.json"),
)
.data(b"any bytes".to_vec()),
)
.append(FileSystem::mkfile(
OutputIdx(1),
LayerPath::Scratch("/build-graph.json"),
))
.append(FileSystem::mkfile(
OutputIdx(2),
LayerPath::Own(OwnOutputIdx(1), "/llb.pb"),
));
crate::check_op!(
operation,
|digest| { "sha256:9c0d9f741dfc9b4ea8d909ebf388bc354da0ee401eddf5633e8e4ece7e87d22d" },
|description| { vec![] },
|caps| { vec!["file.base"] },
|cached_tail| {
vec!["sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702"]
},
|inputs| {
vec![(
"sha256:a60212791641cbeaa3a49de4f7dff9e40ae50ec19d1be9607232037c1db16702",
0,
)]
},
|op| {
Op::File(FileOp {
actions: vec![
FileAction {
input: 0,
secondary_input: -1,
output: 0,
action: Some(Action::Mkfile(FileActionMkFile {
path: "/build-plan.json".into(),
owner: None,
mode: -1,
timestamp: -1,
data: b"any bytes".to_vec(),
})),
},
FileAction {
input: -1,
secondary_input: -1,
output: 1,
action: Some(Action::Mkfile(FileActionMkFile {
path: "/build-graph.json".into(),
owner: None,
mode: -1,
timestamp: -1,
data: vec![],
})),
},
FileAction {
input: 2,
secondary_input: -1,
output: 2,
action: Some(Action::Mkfile(FileActionMkFile {
path: "/llb.pb".into(),
owner: None,
mode: -1,
timestamp: -1,
data: vec![],
})),
},
],
})
},
);
}