summaryrefslogtreecommitdiff
path: root/tests/archive.rs
blob: 6f803ef817539fe329eb1686c71c72117f5c6ae6 (plain)
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
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 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");
}