1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
use std::fs;
use std::os::unix::fs::symlink;
use std::os::unix::net::UnixListener;
use alt_controller_image::archive::NativeTarWriter;
use tar::Archive;
use tempfile::tempdir;
#[test]
fn writes_deterministic_tar_with_files_directories_and_symlinks() {
let fixture = tempdir().expect("fixture directory");
let rootfs = fixture.path().join("rootfs");
fs::create_dir_all(rootfs.join("etc/nested")).expect("create rootfs tree");
fs::write(rootfs.join("etc/nested/config"), "controller=true\n").expect("write config");
symlink("nested/config", rootfs.join("etc/config")).expect("create symlink");
fs::create_dir_all(rootfs.join(".host/private")).expect("create host metadata");
fs::write(rootfs.join(".host/private/key"), "excluded").expect("write host metadata");
fs::create_dir_all(rootfs.join("var/.fakedata")).expect("create fake metadata");
fs::write(rootfs.join("var/.fakedata/db"), "excluded").expect("write fake metadata");
let first = fixture.path().join("first.tar");
let second = fixture.path().join("second.tar");
NativeTarWriter::new().write(&rootfs, &first).expect("write first archive");
NativeTarWriter::new().write(&rootfs, &second).expect("write second archive");
assert_eq!(fs::read(&first).expect("read first"), fs::read(&second).expect("read second"));
let mut archive = Archive::new(fs::File::open(first).expect("open archive"));
let entries = archive
.entries()
.expect("read archive entries")
.map(|entry| {
let entry = entry.expect("read entry");
let path = entry.path().expect("entry path").into_owned();
(path, entry.header().entry_type(), entry.header().mtime().expect("mtime"), entry.header().uid().expect("uid"), entry.header().gid().expect("gid"))
})
.collect::<Vec<_>>();
assert_eq!(
entries,
vec![
("etc".into(), tar::EntryType::Directory, 0, 0, 0),
("etc/config".into(), tar::EntryType::Symlink, 0, 0, 0),
("etc/nested".into(), tar::EntryType::Directory, 0, 0, 0),
("etc/nested/config".into(), tar::EntryType::Regular, 0, 0, 0),
("var".into(), tar::EntryType::Directory, 0, 0, 0),
]
);
}
#[test]
fn preserves_hardlinks_in_the_archive() {
use std::os::unix::fs::MetadataExt;
let fixture = tempdir().expect("fixture directory");
let rootfs = fixture.path().join("rootfs");
fs::create_dir_all(rootfs.join("usr/bin")).expect("create rootfs tree");
let original = rootfs.join("usr/bin/controller");
let alias = rootfs.join("usr/bin/controller-link");
fs::write(&original, "controller\n").expect("write controller");
fs::hard_link(&original, &alias).expect("create hardlink");
assert_eq!(
fs::metadata(&original).expect("stat original").ino(),
fs::metadata(&alias).expect("stat alias").ino()
);
let artifact = fixture.path().join("controller.tar");
NativeTarWriter::new()
.write(&rootfs, &artifact)
.expect("write archive");
let mut archive = Archive::new(fs::File::open(artifact).expect("open archive"));
let entries = archive
.entries()
.expect("read archive entries")
.map(|entry| {
let entry = entry.expect("read entry");
(
entry.path().expect("entry path").into_owned(),
entry.header().entry_type(),
entry.link_name().expect("entry link name").map(|path| path.into_owned()),
)
})
.collect::<Vec<_>>();
assert_eq!(
entries,
vec![
("usr".into(), tar::EntryType::Directory, None),
("usr/bin".into(), tar::EntryType::Directory, None),
("usr/bin/controller".into(), tar::EntryType::Regular, None),
(
"usr/bin/controller-link".into(),
tar::EntryType::Link,
Some("usr/bin/controller".into())
),
]
);
}
#[test]
fn rejects_a_missing_rootfs() {
let fixture = tempdir().expect("fixture directory");
let error = NativeTarWriter::new()
.write(fixture.path().join("missing"), fixture.path().join("image.tar"))
.expect_err("missing rootfs must fail");
assert!(error.to_string().contains("rootfs is not a directory"));
}
#[test]
fn removes_partial_output_when_an_unsupported_entry_stops_packaging() {
let fixture = tempdir().expect("fixture directory");
let rootfs = fixture.path().join("rootfs");
fs::create_dir(&rootfs).expect("create rootfs");
let _socket = UnixListener::bind(rootfs.join("image-builder.sock")).expect("create socket");
let artifact = fixture.path().join("image.tar");
let error = NativeTarWriter::new()
.write(&rootfs, &artifact)
.expect_err("socket entries must be rejected");
assert!(error.to_string().contains("unsupported rootfs entry type"));
assert!(!artifact.exists(), "failed packaging must not publish a partial archive");
}
|