Skip to content

Conversation

@userhaptop
Copy link

@userhaptop userhaptop commented Feb 7, 2026

  • Add Ubuntu distro to Distro enum
  • Implement Ubuntu struct with ImageAction trait
  • Use pre-extracted kernel/initrd from official Ubuntu cloud images
  • No guestfish dependency - WSL compatible
  • Add integration test for Ubuntu image creation

This implementation downloads pre-extracted boot files directly from Ubuntu's official cloud image repository, avoiding the need for libguestfs/guestfish tools. This makes it 100% compatible with WSL environments where guestfish is problematic.

test:
cargo test
test image::tests::test_find_sha512_for_exact_filename ... ok
test image::tests::test_distro_enum_variants ... ok

cargo test test_ubuntu_image_creation -- --ignored --nocapture
✅ Ubuntu image created successfully!
Image: /home/my_user/.local/share/qlean/images/ubuntu-noble-cloudimg/ubuntu-noble-cloudimg.qcow2
Kernel: /home/my_user/.local/share/qlean/images/ubuntu-noble-cloudimg/vmlinuz
Initrd: /home/my_user/.local/share/qlean/images/ubuntu-noble-cloudimg/initrd.img
test test_ubuntu_image_creation ... ok

Signed-off-by: userhaptop <1307305157@qq.com>
Signed-off-by: userhaptop <1307305157@qq.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds initial Ubuntu cloud-image support to qlean’s image pipeline by introducing a new distro variant and a download/extract strategy that avoids libguestfs extraction tools by consuming Ubuntu’s pre-unpacked boot artifacts.

Changes:

  • Add Ubuntu to the Distro enum and extend Image / create_image to support it.
  • Implement an Ubuntu ImageAction that downloads the qcow2 plus pre-extracted kernel/initrd from Ubuntu’s cloud image repo.
  • Add an ignored, serialized integration test that validates Ubuntu image + boot artifacts are created on disk.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
src/image.rs Adds Ubuntu distro variant and implementation; wires Ubuntu into Image and create_image; adds a small enum test.
tests/ubuntu_image.rs Adds an ignored integration test that exercises Ubuntu image creation and validates output files exist.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +371 to +377
// Ubuntu noble (24.04 LTS) cloud image base URL
let base_url = "https://cloud-images.ubuntu.com/noble/current";

// Download qcow2 image
let qcow2_url = format!("{}/noble-server-cloudimg-amd64.img", base_url);
let qcow2_path = image_dir.join(format!("{}.qcow2", name));
download_file(&qcow2_url, &qcow2_path).await?;
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ubuntu::download hard-codes the remote release/arch (noble, amd64) and ignores the name argument when selecting what to download. This makes create_image(Distro::Ubuntu, name) misleading because any name will still fetch noble and then be cached under that name. Consider parsing name into (release, variant, arch) or introducing explicit parameters/config for Ubuntu images, or at minimum validate that name matches the hard-coded remote artifact to avoid accidental mismatches.

Copilot uses AI. Check for mistakes.
Comment on lines +418 to +434
async fn download_file(url: &str, dest: &PathBuf) -> Result<()> {
debug!("Downloading {} to {}", url, dest.display());
let response = reqwest::get(url)
.await
.with_context(|| format!("failed to download from {}", url))?;

let mut file = File::create(dest)
.await
.with_context(|| format!("failed to create file at {}", dest.display()))?;

let mut stream = response.bytes_stream();
while let Some(chunk) = stream.next().await {
let chunk = chunk.with_context(|| "failed to read chunk from stream")?;
file.write_all(&chunk)
.await
.with_context(|| "failed to write to file")?;
}
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

download_file() does not validate the HTTP status code before writing the body to disk. If Ubuntu returns a 404/500 HTML error page, it will be saved as the qcow2/kernel/initrd and later treated as a valid cached image (since the checksum file is generated from whatever was downloaded). Use error_for_status() (or equivalent) before streaming the body, and consider failing early if the response is not successful.

Copilot uses AI. Check for mistakes.
Comment on lines +371 to +395
// Ubuntu noble (24.04 LTS) cloud image base URL
let base_url = "https://cloud-images.ubuntu.com/noble/current";

// Download qcow2 image
let qcow2_url = format!("{}/noble-server-cloudimg-amd64.img", base_url);
let qcow2_path = image_dir.join(format!("{}.qcow2", name));
download_file(&qcow2_url, &qcow2_path).await?;

// Download pre-extracted kernel
let kernel_url = format!(
"{}/unpacked/noble-server-cloudimg-amd64-vmlinuz-generic",
base_url
);
let kernel_path = image_dir.join("vmlinuz");
download_file(&kernel_url, &kernel_path).await?;

// Download pre-extracted initrd
let initrd_url = format!(
"{}/unpacked/noble-server-cloudimg-amd64-initrd-generic",
base_url
);
let initrd_path = image_dir.join("initrd.img");
download_file(&initrd_url, &initrd_path).await?;

Ok(())
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ubuntu downloads are not verified against Ubuntu-published checksums/signatures. Because the project writes a local checksum file after download, a corrupted or intercepted download will be permanently “trusted” on subsequent runs. Fetch and validate against Ubuntu’s published checksum/sig files for the image (and ideally the unpacked kernel/initrd too, if checksums are available), or otherwise add an external integrity verification step before saving metadata.

Copilot uses AI. Check for mistakes.
Comment on lines +482 to +491
pub async fn create_image(distro: Distro, name: &str) -> Result<Image> {
match distro {
Distro::Debian => {
let image = ImageMeta::<Debian>::create(name).await?;
Ok(Image::Debian(image))
} // Add more distros as needed
}// Add more distros as needed
Distro::Ubuntu => {
let image = ImageMeta::<Ubuntu>::create(name).await?;
Ok(Image::Ubuntu(image))
}
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description claims “No guestfish dependency / WSL compatible”, but the library still globally requires guestfish and virt-copy-out via prerequisite checks before running (see ensure_prerequisites usage). If Ubuntu is intended to work without these tools, consider making prerequisite checks distro-aware (only require guestfish/virt-copy-out for distros that need extraction) or adjusting the claim/documentation to match actual behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +577 to +582
#[test]
fn test_distro_enum_variants() {
let variants = vec![Distro::Debian, Distro::Ubuntu];
assert_eq!(variants.len(), 2);
}

Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_distro_enum_variants only asserts the length of a manually constructed vec of variants, which doesn’t validate behavior and will create churn whenever a new distro is added. Consider removing it, or replace it with a test that validates a real invariant (e.g., that create_image handles each enum variant, or that serialization/deserialization round-trips for known variants).

Suggested change
#[test]
fn test_distro_enum_variants() {
let variants = vec![Distro::Debian, Distro::Ubuntu];
assert_eq!(variants.len(), 2);
}

Copilot uses AI. Check for mistakes.
Signed-off-by: userhaptop <1307305157@qq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant