use UUID;
use POSIX;
use IO::Socket::IP;
+use URI::Escape;
use PVE::Cluster qw (cfs_read_file cfs_write_file);;
use PVE::SafeSyslog;
use PVE::Network;
use PVE::Firewall;
use PVE::API2::Firewall::VM;
+use PVE::API2::Qemu::Agent;
BEGIN {
if (!$ENV{PVE_GENERATING_DOCS}) {
my $volid = $drive->{file};
- if (!$volid || $volid eq 'none') {
+ if (!$volid || ($volid eq 'none' || $volid eq 'cloudinit')) {
+ # nothing to check
+ } elsif ($volid =~ m/^(([^:\s]+):)?(cloudinit)$/) {
# nothing to check
} elsif ($isCDROM && ($volid eq 'cdrom')) {
$rpcenv->check($authuser, "/", ['Sys.Console']);
if (!$volid || $volid eq 'none' || $volid eq 'cdrom') {
delete $disk->{size};
$res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
+ } elsif ($volid =~ m!^(?:([^/:\s]+):)?cloudinit$!) {
+ my $storeid = $1 || $default_storage;
+ die "no storage ID specified (and no default storage)\n" if !$storeid;
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+ my $name = "vm-$vmid-cloudinit";
+ my $fmt = undef;
+ if ($scfg->{path}) {
+ $name .= ".qcow2";
+ $fmt = 'qcow2';
+ }else{
+ $fmt = 'raw';
+ }
+ # FIXME: Reasonable size? qcow2 shouldn't grow if the space isn't used anyway?
+ my $cloudinit_iso_size = 5; # in MB
+ my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid,
+ $fmt, $name, $cloudinit_iso_size*1024);
+ $disk->{file} = $volid;
+ $disk->{media} = 'cdrom';
+ push @$vollist, $volid;
+ delete $disk->{format}; # no longer needed
+ $res->{$ds} = PVE::QemuServer::print_drive($vmid, $disk);
} elsif ($volid =~ $NEW_DISK_RE) {
my ($storeid, $size) = ($2 || $default_storage, $3);
die "no storage ID specified (and no default storage)\n" if !$storeid;
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
} elsif ($diskoptions->{$opt}) {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
- } elsif ($opt =~ m/^net\d+$/) {
+ } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
} else {
# catches usb\d+, hostpci\d+, args, lock, etc.
my $storecfg = PVE::Storage::config();
+ if (defined(my $ssh_keys = $param->{sshkeys})) {
+ $ssh_keys = URI::Escape::uri_unescape($ssh_keys);
+ PVE::Tools::validate_ssh_public_keys($ssh_keys);
+ }
+
PVE::Cluster::check_cfs_quorum();
if (defined($pool)) {
path => '{vmid}/firewall',
});
+__PACKAGE__->register_method ({
+ subclass => "PVE::API2::Qemu::Agent",
+ path => '{vmid}/agent',
+});
+
__PACKAGE__->register_method({
name => 'rrd',
path => '{vmid}/rrd',
delete $conf->{pending};
+ # hide cloudinit password
+ if ($conf->{cipassword}) {
+ $conf->{cipassword} = '**********';
+ }
+
return $conf;
}});
$item->{value} = $conf->{$opt} if defined($conf->{$opt});
$item->{pending} = $conf->{pending}->{$opt} if defined($conf->{pending}->{$opt});
$item->{delete} = ($pending_delete_hash->{$opt} ? 2 : 1) if exists $pending_delete_hash->{$opt};
+
+ # hide cloudinit password
+ if ($opt eq 'cipassword') {
+ $item->{value} = '**********' if defined($item->{value});
+ # the trailing space so that the pending string is different
+ $item->{pending} = '********** ' if defined($item->{pending});
+ }
push @$res, $item;
}
next if defined($conf->{$opt});
my $item = { key => $opt };
$item->{pending} = $conf->{pending}->{$opt};
+
+ # hide cloudinit password
+ if ($opt eq 'cipassword') {
+ $item->{pending} = '**********' if defined($item->{pending});
+ }
push @$res, $item;
}
my $background_delay = extract_param($param, 'background_delay');
+ if (defined(my $cipassword = $param->{cipassword})) {
+ # Same logic as in cloud-init (but with the regex fixed...)
+ $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword)
+ if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
+ }
+
my @paramarr = (); # used for log message
foreach my $key (sort keys %$param) {
- push @paramarr, "-$key", $param->{$key};
+ my $value = $key eq 'cipassword' ? '<hidden>' : $param->{$key};
+ push @paramarr, "-$key", $value;
}
my $skiplock = extract_param($param, 'skiplock');
my $force = extract_param($param, 'force');
+ if (defined(my $ssh_keys = $param->{sshkeys})) {
+ $ssh_keys = URI::Escape::uri_unescape($ssh_keys);
+ PVE::Tools::validate_ssh_public_keys($ssh_keys);
+ }
+
die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);
my $storecfg = PVE::Storage::config();
if (PVE::QemuServer::is_valid_drivename($opt)) {
my $drive = PVE::QemuServer::parse_drive($opt, $param->{$opt});
+ # FIXME: cloudinit: CDROM or Disk?
if (PVE::QemuServer::drive_is_cdrom($drive)) { # CDROM
$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
} else {
if ($conf->{vga} && ($conf->{vga} =~ m/^serial\d+$/)) {
- my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga} ];
+ my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga}, '-escape', '0' ];
$cmd = ['/usr/bin/vncterm', '-rfbport', $port,
'-timeout', $timeout, '-authpath', $authpath,
my $remcmd = $remip ?
['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
- my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid];
+ my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-escape', '0'];
push @$termcmd, '-iface', $serial if $serial;
my $realcmd = sub {
$status->{spice} = 1 if PVE::QemuServer::vga_conf_has_spice($conf->{vga});
+ $status->{agent} = 1 if $conf->{agent};
+
return $status;
}});
# read spice ticket from STDIN
my $spice_ticket;
if ($stateuri && ($stateuri eq 'tcp') && $migratedfrom && ($rpcenv->{type} eq 'cli')) {
- if (defined(my $line = <>)) {
+ if (defined(my $line = <STDIN>)) {
chomp $line;
$spice_ticket = $line;
}
} elsif (PVE::QemuServer::is_valid_drivename($opt)) {
my $drive = PVE::QemuServer::parse_drive($opt, $value);
die "unable to parse drive options for '$opt'\n" if !$drive;
- if (PVE::QemuServer::drive_is_cdrom($drive)) {
+ if (PVE::QemuServer::drive_is_cdrom($drive, 1)) {
$newconf->{$opt} = $value; # simply copy configuration
} else {
- if ($param->{full}) {
+ if ($param->{full} || PVE::QemuServer::drive_is_cloudinit($drive)) {
die "Full clone feature is not supported for drive '$opt'\n"
if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
$fullclone->{$opt} = 1;
my $old_volid = $drive->{file} || die "disk '$disk' has no associated volume\n";
- die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
+ die "you can't move a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive, 1);
my $oldfmt;
my ($oldstoreid, $oldvolname) = PVE::Storage::parse_volume_id($old_volid);
return $res;
}});
-my $guest_agent_commands = [
- 'ping',
- 'get-time',
- 'info',
- 'fsfreeze-status',
- 'fsfreeze-freeze',
- 'fsfreeze-thaw',
- 'fstrim',
- 'network-get-interfaces',
- 'get-vcpus',
- 'get-fsinfo',
- 'get-memory-blocks',
- 'get-memory-block-info',
- 'suspend-hybrid',
- 'suspend-ram',
- 'suspend-disk',
- 'shutdown',
- ];
-
-__PACKAGE__->register_method({
- name => 'agent',
- path => '{vmid}/agent',
- method => 'POST',
- protected => 1,
- proxyto => 'node',
- description => "Execute Qemu Guest Agent commands.",
- permissions => {
- check => ['perm', '/vms/{vmid}', [ 'VM.Monitor' ]],
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- node => get_standard_option('pve-node'),
- vmid => get_standard_option('pve-vmid', {
- completion => \&PVE::QemuServer::complete_vmid_running }),
- command => {
- type => 'string',
- description => "The QGA command.",
- enum => $guest_agent_commands,
- },
- },
- },
- returns => {
- type => 'object',
- description => "Returns an object with a single `result` property. The type of that
-property depends on the executed command.",
- },
- code => sub {
- my ($param) = @_;
-
- my $vmid = $param->{vmid};
-
- my $conf = PVE::QemuConfig->load_config ($vmid); # check if VM exists
-
- die "No Qemu Guest Agent\n" if !defined($conf->{agent});
- die "VM $vmid is not running\n" if !PVE::QemuServer::check_running($vmid);
-
- my $cmd = $param->{command};
-
- my $res = PVE::QemuServer::vm_mon_cmd($vmid, "guest-$cmd");
-
- return { result => $res };
- }});
-
__PACKAGE__->register_method({
name => 'resize_vm',
path => '{vmid}/resize',