use crate::args;
use crate::container::{self, Container};
use crate::errors::*;
use crate::fetch;
use crate::lockfile::PackageLock;
use crate::paths;
use data_encoding::BASE64;
use std::collections::HashMap;
use std::env;
use std::path::Path;
use tempfile::TempDir;
use tokio::fs;
pub async fn setup_extra_folder(
path: &Path,
dependencies: &[PackageLock],
) -> Result<HashMap<String, Vec<String>>> {
let pkgs_cache_dir = paths::pkgs_cache_dir()?;
let mut install = HashMap::<_, Vec<_>>::new();
for package in dependencies {
let url = package
.url
.parse::<reqwest::Url>()
.with_context(|| anyhow!("Failed to parse string as url: {:?}", package.url))?;
let filename = url
.path_segments()
.context("Failed to get path from url")?
.last()
.context("Failed to find filename from url")?;
if filename.is_empty() {
bail!("Filename from url is empty");
}
let source = pkgs_cache_dir.sha256_path(&package.sha256)?;
let dest = path.join(filename);
let dest_sig = path.join(filename.to_owned() + ".sig");
debug!("Trying to reflink {source:?} -> {dest:?}...");
if let Err(err) = clone_file::clone_file(&source, &dest) {
debug!("Failed to reflink, trying traditional copy: {err:#}");
fs::copy(&source, &dest)
.await
.context("Failed to copy package from cache to temporary folder")?;
}
match package.system.as_str() {
"alpine" => (),
"archlinux" => {
let signature = package
.signature
.as_ref()
.context("Package in dependency lockfile is missing signature")?;
let signature = BASE64
.decode(signature.as_bytes())
.context("Failed to decode signature as base64")?;
debug!(
"Writing signature ({} bytes) to {dest_sig:?}...",
signature.len()
);
fs::write(dest_sig, signature).await?;
}
"debian" => (),
system => bail!("Unknown package system: {system:?}"),
}
let pkg = fs::read(&dest).await?;
fetch::verify_pin_metadata(&pkg, package)
.with_context(|| anyhow!("Failed to verify metadata for {filename:?}"))?;
install
.entry(package.system.clone())
.or_default()
.push(filename.to_string());
}
Ok(install)
}
pub async fn run_build(
container: &Container,
build: &args::Build,
extra: Option<&(TempDir, HashMap<String, Vec<String>>)>,
) -> Result<()> {
if let Some((_, pkgs)) = extra {
for (system, pkgs) in pkgs {
match system.as_str() {
"alpine" => {
let mut cmd = vec![
"apk".to_string(),
"add".to_string(),
"--no-network".to_string(),
"--".to_string(),
];
for pkg in pkgs {
cmd.push(format!("/extra/{pkg}"));
}
info!("Installing dependencies...");
container.exec(&cmd, container::Exec::default()).await?;
}
"archlinux" => {
let mut cmd = vec![
"pacman".to_string(),
"-U".to_string(),
"--noconfirm".to_string(),
"--".to_string(),
];
for pkg in pkgs {
cmd.push(format!("/extra/{pkg}"));
}
info!("Installing dependencies...");
container.exec(&cmd, container::Exec::default()).await?;
}
"debian" => {
let mut cmd = vec![
"apt-get".to_string(),
"install".to_string(),
"--".to_string(),
];
for pkg in pkgs {
cmd.push(format!("/extra/{pkg}"));
}
info!("Installing dependencies...");
container.exec(&cmd, container::Exec::default()).await?;
}
system => bail!("Unknown package system: {system:?}"),
}
}
}
info!("Running build...");
container
.exec(
&build.cmd,
container::Exec {
cwd: Some("/build"),
env: &build.env,
..Default::default()
},
)
.await?;
Ok(())
}
pub async fn build(build: &args::Build) -> Result<()> {
container::test_for_unprivileged_userns_clone().await?;
build.validate()?;
let (manifest, lockfile) = build.load_files().await?;
if let Some(manifest) = &manifest {
if let Err(err) = manifest.satisfied_by(&lockfile) {
warn!("Lockfile might be out-of-sync: {err:#}");
}
}
let pwd = env::current_dir()?;
let pwd = pwd
.into_os_string()
.into_string()
.map_err(|_| anyhow!("Failed to convert current path to utf-8"))?;
let mut mounts = vec![(pwd, "/build".to_string())];
let dependencies = lockfile
.packages
.into_iter()
.filter(|p| !p.installed)
.collect::<Vec<_>>();
let extra = if !dependencies.is_empty() {
fetch::download_dependencies(&dependencies).await?;
let path = paths::repro_env_dir()?;
let temp_dir = tempfile::Builder::new().prefix("env.").tempdir_in(path)?;
let pkgs = setup_extra_folder(temp_dir.path(), &dependencies).await?;
let path = temp_dir
.path()
.to_owned()
.into_os_string()
.into_string()
.map_err(|_| anyhow!("Failed to convert temporary path to utf-8"))?;
mounts.push((path, "/extra".to_string()));
Some((temp_dir, pkgs))
} else {
None
};
let container = Container::create(
&lockfile.container.image,
container::Config {
mounts: &mounts,
expose_fuse: false,
},
)
.await?;
container
.run(run_build(&container, build, extra.as_ref()), build.keep)
.await
}