summaryrefslogtreecommitdiff
path: root/src/build.rs
blob: fcacfc09885ef80e56de64c2ed810d4b28875c83 (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
use std::path::{Path, PathBuf};

use anyhow::{bail, Context, Result};

use crate::archive::NativeTarWriter;
use crate::initramfs::{InitramfsBuilder, InitramfsRequest, InitramfsResult};
use crate::manifest::{ArtifactManifest, InitrdRecord};
use crate::package_installer::PackageInstaller;
use crate::rootfs::{InitrdOem, RootfsFinalization};
use crate::BuildPlan;

/// Executes the native stages after a plan has been validated.
///
/// The only platform facilities are injected package and initramfs adapters;
/// filesystem finalization, archive writing, and manifest collection are native.
#[derive(Debug)]
pub struct BuildExecutor<I, B> {
    installer: I,
    initramfs_builder: B,
    tar_writer: NativeTarWriter,
}

impl<I, B> BuildExecutor<I, B> {
    pub fn new(installer: I, initramfs_builder: B) -> Self {
        Self { installer, initramfs_builder, tar_writer: NativeTarWriter::new() }
    }
}

impl<I: PackageInstaller, B: InitramfsBuilder> BuildExecutor<I, B> {
    pub fn execute(
        &mut self,
        plan: &BuildPlan,
        workspace: impl AsRef<Path>,
        apt_config: impl AsRef<Path>,
        artifact: impl AsRef<Path>,
    ) -> Result<BuildResult> {
        let workspace = workspace.as_ref();
        if workspace.exists() {
            bail!("workspace already exists; refusing to reuse it: {}", workspace.display());
        }
        let artifact = artifact.as_ref();
        if artifact.exists() {
            bail!("output artifact already exists; refusing to overwrite it: {}", artifact.display());
        }
        let manifest = ArtifactManifest::path_beside(artifact)?;
        if manifest.exists() {
            bail!("output manifest already exists; refusing to overwrite it: {}", manifest.display());
        }
        let parent = artifact
            .parent()
            .filter(|parent| !parent.as_os_str().is_empty())
            .ok_or_else(|| anyhow::anyhow!("artifact path has no parent: {}", artifact.display()))?;
        if parent.exists() && !parent.is_dir() {
            bail!("artifact parent is not a directory: {}", parent.display());
        }

        let request = plan.package_request(workspace, apt_config)?;
        std::fs::create_dir_all(workspace)
            .with_context(|| format!("create workspace {}", workspace.display()))?;
        self.installer.install(&request)?;

        let rootfs = workspace.join("chroot");
        if !rootfs.is_dir() {
            bail!("package installer did not create rootfs: {}", rootfs.display());
        }
        RootfsFinalization::new(
            Vec::new(),
            InitrdOem::new(
                plan.spec().boot.initrd_features.iter().cloned(),
                plan.spec().boot.initrd_modules.iter().cloned(),
            ),
            Vec::new(),
        )
        .apply(&rootfs)?;

        let initrd = self.initramfs_builder.build(&InitramfsRequest::discover(&rootfs)?)?;
        std::fs::create_dir_all(parent)
            .with_context(|| format!("create artifact directory {}", parent.display()))?;
        self.tar_writer.write(&rootfs, artifact)?;
        let manifest = ArtifactManifest::collect(
            &rootfs,
            Vec::new(),
            Some(InitrdRecord::new(
                initrd.initrd_path().display().to_string(),
                initrd.sha256(),
            )),
            artifact,
        )?;
        let manifest_path = manifest.write_beside(artifact)?;

        Ok(BuildResult {
            artifact: artifact.to_path_buf(),
            manifest: manifest_path,
            initrd: Some(initrd),
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BuildResult {
    artifact: PathBuf,
    manifest: PathBuf,
    initrd: Option<InitramfsResult>,
}

impl BuildResult {
    pub fn artifact(&self) -> PathBuf {
        self.artifact.clone()
    }

    pub fn manifest(&self) -> &Path {
        &self.manifest
    }

    pub fn initrd(&self) -> Option<&InitramfsResult> {
        self.initrd.as_ref()
    }
}