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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
|
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 rejects_an_archive_output_inside_the_rootfs() {
let fixture = tempdir().expect("fixture directory");
let rootfs = fixture.path().join("rootfs");
fs::create_dir_all(rootfs.join("etc")).expect("create rootfs");
fs::write(rootfs.join("etc/controller.conf"), "controller=true\n").expect("write rootfs file");
let artifact = rootfs.join("controller.tar");
let error = NativeTarWriter::new()
.write(&rootfs, &artifact)
.expect_err("archive output inside rootfs must be rejected");
assert!(
error
.to_string()
.contains("archive output must not be inside rootfs")
);
assert!(!artifact.exists());
assert!(!rootfs.join("controller.tar.partial").exists());
}
#[test]
fn refuses_to_replace_an_existing_archive() {
let fixture = tempdir().expect("fixture directory");
let rootfs = fixture.path().join("rootfs");
fs::create_dir(&rootfs).expect("create rootfs");
fs::write(rootfs.join("controller.conf"), "controller=true\n").expect("write rootfs file");
let artifact = fixture.path().join("controller.tar");
fs::write(&artifact, "existing artifact").expect("write existing artifact");
let error = NativeTarWriter::new()
.write(&rootfs, &artifact)
.expect_err("an existing archive must not be replaced");
assert!(error.to_string().contains("archive output already exists"));
assert_eq!(
fs::read_to_string(&artifact).expect("read existing artifact"),
"existing artifact"
);
assert!(!fixture.path().join("controller.tar.partial").exists());
}
#[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"
);
}
|