Skip to content

Commit

Permalink
Merge pull request #43 from gevulotnetwork/feature/unpack-image-witho…
Browse files Browse the repository at this point in the history
…ut-layers

Dump container filesystem instead of extracting layers
  • Loading branch information
koxu1996 authored Jan 31, 2025
2 parents 259985b + d3c5fd0 commit 6f3992d
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 59 deletions.
2 changes: 1 addition & 1 deletion src/builders/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use anyhow::Result;
use crate::build::BuildArgs;

pub mod nvidia;
pub mod skopeo_builder;
pub mod podman_builder;

pub trait ImageBuilder {
fn build(&self, options: &BuildOptions) -> Result<()>;
Expand Down
10 changes: 5 additions & 5 deletions src/builders/nvidia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use log::{debug, info};
use tempdir::TempDir;
use thiserror::Error;

use crate::builders::skopeo_builder::SkopeoSyslinuxBuilder;
use crate::builders::podman_builder::PodmanSyslinuxBuilder;

#[derive(Error, Debug)]
pub enum NvidiaError {
Expand Down Expand Up @@ -139,7 +139,7 @@ fn run_driver_container(
let kernel_source_copy_str = path_to_str(kernel_source_copy.path())?;
let target_dir_str = path_to_str(target_dir.path())?;

SkopeoSyslinuxBuilder::run_command(
PodmanSyslinuxBuilder::run_command(
&[
"podman",
"run",
Expand Down Expand Up @@ -171,7 +171,7 @@ fn prepare_vm_modules_dir<P: AsRef<Path>>(
let vm_modules_dir_str = path_to_str(&vm_modules_dir)?;
// NOTE: We cannot use plain `fs::create_dir_all`, as skopeo creates root-owned directory for the VM.
// This could be fixed in the future with a template + chown.
SkopeoSyslinuxBuilder::run_command(
PodmanSyslinuxBuilder::run_command(
&["sh", "-c", &format!("mkdir -p {}", vm_modules_dir_str)],
true,
)
Expand Down Expand Up @@ -226,7 +226,7 @@ fn build_module_dependencies<P: AsRef<Path>>(
let vm_root_path_str = path_to_str(vm_root_path.as_ref())?;
// NOTE: We have to use sudo, as skopeo creates root-owned directory for the VM.
// This could be fixed in the future with a template + chown.
SkopeoSyslinuxBuilder::run_command(
PodmanSyslinuxBuilder::run_command(
&[
"depmod",
"--basedir",
Expand All @@ -251,7 +251,7 @@ fn copy_file<P: AsRef<Path>>(

// NOTE: We cannot use plain `fs::copy`, as skopeo creates root-owned directory for the VM.
// This could be fixed in the future with a template + chown.
SkopeoSyslinuxBuilder::run_command(
PodmanSyslinuxBuilder::run_command(
&[
"sh",
"-c",
Expand Down
130 changes: 79 additions & 51 deletions src/builders/skopeo_builder.rs → src/builders/podman_builder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::{Context, Result};
use log::debug;
use mia_installer::runtime_config::{self, RuntimeConfig};
use oci_spec::image::{ImageConfiguration, ImageManifest};
use oci_spec::image::Config;
use std::io::{self, BufRead, BufReader, Write};
use std::{env, fs, path::Path, process::Command};
use tempdir::TempDir;
Expand All @@ -10,9 +10,11 @@ use crate::builders::{BuildOptions, ImageBuilder};

use super::nvidia;

pub struct SkopeoSyslinuxBuilder {}
const TEMP_CONTAINER_NAME: &str = "gevulot-temp-container";

impl ImageBuilder for SkopeoSyslinuxBuilder {
pub struct PodmanSyslinuxBuilder {}

impl ImageBuilder for PodmanSyslinuxBuilder {
fn build(&self, options: &BuildOptions) -> Result<()> {
// Handle printing messages with regard to `quiet` option.
let print = |line: &str| -> Result<()> {
Expand Down Expand Up @@ -178,7 +180,7 @@ impl ImageBuilder for SkopeoSyslinuxBuilder {
}
}

impl SkopeoSyslinuxBuilder {
impl PodmanSyslinuxBuilder {
// Create an empty disk image file of the specified size
fn create_disk_image(size: &str, output_file: &str) -> Result<()> {
Self::run_command(&["truncate", "-s", size, output_file], false)
Expand Down Expand Up @@ -310,60 +312,52 @@ impl SkopeoSyslinuxBuilder {
// This temp dir will be removed on dropping.
let target_dir = TempDir::new("").context("Failed to create temporary directory")?;

// Copy the container image to a directory
// Create a container to obtain full filesystem, instead of dealing with layers.
Self::run_command(
&[
"skopeo",
"copy",
"podman",
"create",
"--replace",
"--name",
TEMP_CONTAINER_NAME,
container_source,
&format!("dir:{}", target_dir.path().display()),
],
false,
)
.context("Failed to copy container image")?;

// Read image manifest
let manifest = ImageManifest::from_file(target_dir.path().join("manifest.json"))
.context("Failed to read image manifest")?;

// Extract all layers of image into target dir
for layer in manifest.layers() {
let layer_path = target_dir.path().join(layer.digest().digest());
log::debug!(
"unpack layer {} from {}",
layer.digest(),
layer_path.display()
);
// Unpack with root permissions
match Self::run_command(
&[
"tar",
"-xf",
layer_path.to_str().unwrap(),
"-C",
target_dir.path().to_str().unwrap(),
],
true,
) {
Ok(_) => {
log::debug!("remove layer {}", layer_path.display());
fs::remove_file(&layer_path).context("Failed to remove layer file")?;
log::debug!("removed layer {}", layer_path.display());
}
Err(_) => {
log::warn!("Failed to unpack layer"); // TODO: Investigate why this happens.
}
};
}
.context("Failed to create container")?;

// Export archive with the container filesystem
let output_file = target_dir.path().join("rootfs.tar");
let output_file_str = output_file.to_str().unwrap();
Self::run_command(
&[
"podman",
"export",
TEMP_CONTAINER_NAME,
"-o",
output_file_str,
],
false,
)
.context("Failed to extract container filesystem")?;

log::debug!("unpacked all layers");
// Remove container - it is no longer necessary as we got filesystem dump.
Self::remove_temporary_container().context("Failed to remove container")?;

// Unpack with root permissions
Self::run_command(
&[
"tar",
"-xf",
output_file_str,
"-C",
target_dir.path().to_str().unwrap(),
],
true,
)
.context("Failed to unpack container filesystem")?;

let config_path = target_dir.path().join(manifest.config().digest().digest());
let config = ImageConfiguration::from_file(&config_path)
.context("Failed to read image configuration")?;
log::debug!("unpacked config {}", config_path.display());
fs::remove_file(&config_path).context("Failed to remove config file")?;
log::debug!("removed config {}", config_path.display());
log::debug!("unpacked container image");

// Copy the extracted rootfs to the mounted filesystem
Self::run_command(
Expand All @@ -383,8 +377,36 @@ impl SkopeoSyslinuxBuilder {
// Ensure all changes are written to disk
Self::run_command(&["sync"], true).context("Failed to sync filesystem")?;

// Extract image config.
let config_path = target_dir.path().join("config.json");
let config_path_str = config_path.to_str().unwrap();
Self::run_command(
&[
"sh",
"-c",
&format!(
"podman image inspect {} > {}",
container_source, config_path_str
),
],
false,
)
.context("Failed to extract image config")?;
log::debug!("full image config dumped to {}", config_path.display());
let config_raw_data =
fs::read_to_string(config_path_str).context("Failed to read image config")?;
let config_json: serde_json::Value = serde_json::from_str(&config_raw_data)
.context("Failed to parse image config as JSON")?;
let config_inner_value: Option<&str> = config_json
.get(0)
.and_then(|item| item.get("Config").and_then(|c| c.as_str()));
let maybe_config: Option<Config> = config_inner_value
.map(serde_json::from_str)
.transpose()
.context("Failed to parse image config")?;

// Extract runtime config from the container manifest.
if let Some(exec_params) = config.config() {
if let Some(exec_params) = maybe_config {
// Add enviromnental variables
if let Some(env_vars) = exec_params.env() {
for var in env_vars {
Expand Down Expand Up @@ -903,6 +925,8 @@ LABEL linux
],
true,
);
// Remove pending container if any.
_ = Self::remove_temporary_container();
// Detach all unused loop devices
_ = Self::run_command(&["losetup", "-D"], true);
log::debug!(
Expand Down Expand Up @@ -973,4 +997,8 @@ LABEL linux
))
}
}

fn remove_temporary_container() -> Result<()> {
Self::run_command(&["podman", "rm", TEMP_CONTAINER_NAME], false)
}
}
4 changes: 2 additions & 2 deletions src/commands/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,11 +267,11 @@ impl BuildArgs {

#[cfg(not(feature = "vm-builder-v2"))]
async fn build(build_args: &BuildArgs) -> Result<Value, Box<dyn std::error::Error>> {
use crate::builders::skopeo_builder::SkopeoSyslinuxBuilder;
use crate::builders::podman_builder::PodmanSyslinuxBuilder;
use crate::builders::{BuildOptions, ImageBuilder};

let options = BuildOptions::from(build_args);
let builder = SkopeoSyslinuxBuilder {};
let builder = PodmanSyslinuxBuilder {};
builder.build(&options)?;

Ok(serde_json::json!({
Expand Down

0 comments on commit 6f3992d

Please sign in to comment.