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
|
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()))?;
if let Err(error) = self.installer.install(&request) {
std::fs::remove_dir_all(workspace)
.with_context(|| format!("remove failed workspace {}", workspace.display()))?;
return Err(error);
}
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()
}
}
|