I'm not sure that it is worth adding code to convert hashes into XML
strings, because upstream we recently started doing releases of a brand
new library 'libvirt-glib', which includes APIs for programatically
building XML documents.
This relies on the Glib::Object::Introspection module, but lets you
write code like
use Glib::Object::Introspection;
Glib::Object::Introspection->setup(basename => 'LibvirtGConfig', version
=> '1.0', package => 'LibvirtGConfig');
LibvirtGConfig::init_check([]);
my $domain = LibvirtGConfig::Domain->new();
$domain->set_name("foo");
$domain->set_memory(1024*1024*1024);
$domain->set_vcpus(2);
$domain->set_lifecycle('on_poweroff', 'destroy');
my $clock = LibvirtGConfig::DomainClock->new();
$clock->set_offset('utc');
$domain->set_clock($clock);
my $os = LibvirtGConfig::DomainOs->new();
$os->set_os_type('hvm');
$os->set_arch("x86_64");
my $devices = [ 'cdrom', 'network' ];
$os->set_boot_devices($devices);
$domain->set_os($os);
my $disk = LibvirtGConfig::DomainDisk->new();
$disk->set_type('file');
$disk->set_guest_device_type('disk');
$disk->set_source("/tmp/foo/bar");
$disk->set_driver_name("qemu");
$disk->set_driver_type("qcow2");
$disk->set_target_bus('ide');
$disk->set_target_dev("hda");
$domain->add_device($disk);
my $interface = LibvirtGConfig::DomainInterfaceNetwork->new();
$interface->set_source("default");
$domain->add_device($interface);
$interface = LibvirtGConfig::DomainInterfaceUser->new();
$interface->set_ifname("eth0");
$interface->set_link_state('up');
$interface->set_mac("00:11:22:33:44:55");
$interface->set_model("foo");
$domain->add_device($interface);
my $input = LibvirtGConfig::DomainInput->new();
$input->set_device_type('tablet');
$input->set_bus('usb');
$domain->add_device($input);
my $graphics = LibvirtGConfig::DomainGraphicsSpice->new();
$graphics->set_port(1234);
$domain->add_device($graphics);
my $video = LibvirtGConfig::DomainVideo->new();
$video->set_model('qxl');
$domain->add_device($video);
my $console = LibvirtGConfig::DomainConsole->new();
my $pty = LibvirtGConfig::DomainChardevSourcePty->new();
$console->set_source($pty);
$domain->add_device($console);
print $domain->to_xml();
my $pool = LibvirtGConfig::StoragePool->new();
$pool->set_pool_type('dir');
my $pool_source = LibvirtGConfig::StoragePoolSource->new();
$pool_source->set_directory("/foo/bar");
$pool->set_source($pool_source);
my $perms = LibvirtGConfig::StoragePermissions->new();
$perms->set_owner(1001);
$perms->set_group(1005);
$perms->set_mode(0744);
$perms->set_label("virt_image_t");
my $pool_target = LibvirtGConfig::StoragePoolTarget->new();
$pool_target->set_path("/dev/disk/by-path");
$pool_target->set_permissions($perms);
$pool->set_target($pool_target);
print $pool->to_xml();
my $vol = LibvirtGConfig::StorageVol->new();
$vol->set_name("my-vol");
$vol->set_capacity(0xdeadbeef);
my $vol_target = LibvirtGConfig::StorageVolTarget->new();
$vol_target->set_format("qcow2");
$vol_target->set_permissions($perms);
$vol->set_target($vol_target);
print $vol->to_xml();