Skip to content

Commit 260c376

Browse files
committed
xtask: Add tmt-provision command
Adds a new xtask command that provisions a VM for manual tmt testing. This wraps bcvk libvirt run, waits for SSH connectivity, and prints connection details for use with tmt provision --how connect. Usage: cargo xtask tmt-provision <image> [vm-name] The command saves the SSH key to target/<vm-name>.ssh-key and prints instructions for running tmt commands and SSH access. Assisted-by: Claude Code (Sonnet 4.5) Signed-off-by: Colin Walters <[email protected]>
1 parent 954fc07 commit 260c376

File tree

1 file changed

+90
-0
lines changed

1 file changed

+90
-0
lines changed

crates/xtask/src/xtask.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const TASKS: &[(&str, fn(&Shell, &[String]) -> Result<()>)] = &[
5151
("package-srpm", package_srpm),
5252
("spec", spec),
5353
("run-tmt", run_tmt),
54+
("tmt-provision", tmt_provision),
5455
];
5556

5657
fn try_main() -> Result<()> {
@@ -410,6 +411,7 @@ fn verify_ssh_connectivity(sh: &Shell, port: u16, key_path: &Utf8Path) -> Result
410411
sh,
411412
"ssh -i {key_path} -p {port_str} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 -o IdentitiesOnly=yes root@localhost 'export TEST=value; whoami'"
412413
)
414+
.ignore_stderr()
413415
.read();
414416

415417
match &result {
@@ -704,6 +706,94 @@ fn run_tmt(sh: &Shell, args: &[String]) -> Result<()> {
704706
Ok(())
705707
}
706708

709+
/// Provision a VM for manual tmt testing
710+
/// Wraps bcvk libvirt run and waits for SSH connectivity
711+
///
712+
/// Arguments:
713+
/// - First arg (required): Image name (e.g. "localhost/bootc-integration")
714+
/// - Second arg (optional): VM name (defaults to "bootc-tmt-manual-<timestamp>")
715+
///
716+
/// Prints SSH connection details for use with tmt provision --how connect
717+
#[context("Provisioning VM for TMT")]
718+
fn tmt_provision(sh: &Shell, args: &[String]) -> Result<()> {
719+
// Check for bcvk
720+
if cmd!(sh, "which bcvk").ignore_status().read().is_err() {
721+
anyhow::bail!("bcvk is not available in PATH");
722+
}
723+
724+
// Parse arguments
725+
if args.is_empty() {
726+
anyhow::bail!("Image name is required as first argument");
727+
}
728+
729+
let image = &args[0];
730+
let vm_name = if args.len() > 1 {
731+
args[1].clone()
732+
} else {
733+
let timestamp = std::time::SystemTime::now()
734+
.duration_since(std::time::UNIX_EPOCH)
735+
.context("Getting timestamp")?
736+
.as_secs();
737+
format!("bootc-tmt-manual-{}", timestamp)
738+
};
739+
740+
println!("Provisioning VM...");
741+
println!(" Image: {}", image);
742+
println!(" VM name: {}\n", vm_name);
743+
744+
// Launch VM with bcvk
745+
// Use ds=iid-datasource-none to disable cloud-init for faster boot
746+
cmd!(sh, "bcvk libvirt run --name {vm_name} --detach --filesystem ext4 --karg=ds=iid-datasource-none {image}")
747+
.run()
748+
.context("Launching VM with bcvk")?;
749+
750+
println!("VM launched, waiting for SSH...");
751+
752+
// Wait for VM to be ready and get SSH info
753+
let (ssh_port, ssh_key) = wait_for_vm_ready(sh, &vm_name)?;
754+
755+
// Save SSH private key to target directory
756+
let key_dir = Utf8Path::new("target");
757+
sh.create_dir(key_dir)
758+
.context("Creating target directory")?;
759+
let key_path = key_dir.join(format!("{}.ssh-key", vm_name));
760+
761+
std::fs::write(&key_path, ssh_key)
762+
.context("Writing SSH key file")?;
763+
764+
// Set proper permissions on key file (0600)
765+
#[cfg(unix)]
766+
{
767+
use std::os::unix::fs::PermissionsExt;
768+
std::fs::set_permissions(&key_path, std::fs::Permissions::from_mode(0o600))
769+
.context("Setting SSH key file permissions")?;
770+
}
771+
772+
println!("SSH key saved to: {}", key_path);
773+
774+
// Verify SSH connectivity
775+
verify_ssh_connectivity(sh, ssh_port, &key_path)?;
776+
777+
println!("\n========================================");
778+
println!("VM provisioned successfully!");
779+
println!("========================================");
780+
println!("VM name: {}", vm_name);
781+
println!("SSH port: {}", ssh_port);
782+
println!("SSH key: {}", key_path);
783+
println!("\nTo use with tmt:");
784+
println!(" tmt run --all provision --how connect \\");
785+
println!(" --guest localhost --port {} \\", ssh_port);
786+
println!(" --user root --key {} \\", key_path);
787+
println!(" plan --name <PLAN_NAME>");
788+
println!("\nTo connect via SSH:");
789+
println!(" ssh -i {} -p {} -o IdentitiesOnly=yes root@localhost", key_path, ssh_port);
790+
println!("\nTo cleanup:");
791+
println!(" bcvk libvirt rm --stop --force {}", vm_name);
792+
println!("========================================\n");
793+
794+
Ok(())
795+
}
796+
707797
fn print_help(_sh: &Shell, _args: &[String]) -> Result<()> {
708798
println!("Tasks:");
709799
for (name, _) in TASKS {

0 commit comments

Comments
 (0)