diff --git a/PVE/API2/Qemu/Agent.pm b/PVE/API2/Qemu/Agent.pm index 499a5919..5dbce8b1 100644 --- a/PVE/API2/Qemu/Agent.pm +++ b/PVE/API2/Qemu/Agent.pm @@ -112,6 +112,8 @@ __PACKAGE__->register_method({ my $cmds = [keys %$guest_agent_commands]; push @$cmds, qw( + exec + exec-status set-user-password ); @@ -250,4 +252,114 @@ __PACKAGE__->register_method({ return { result => $res }; }}); +__PACKAGE__->register_method({ + name => 'exec', + path => 'exec', + method => 'POST', + protected => 1, + proxyto => 'node', + description => "Executes the given command in the vm via the guest-agent and returns an object with the pid.", + 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', + format => 'string-alist', + description => 'The command as a list of program + arguments', + } + }, + }, + returns => { + type => 'object', + properties => { + pid => { + type => 'integer', + description => "The PID of the process started by the guest-agent.", + }, + }, + }, + code => sub { + my ($param) = @_; + + my $vmid = $param->{vmid}; + my $cmd = [PVE::Tools::split_list($param->{command})]; + + my $res = PVE::QemuServer::Agent::qemu_exec($vmid, $cmd); + return $res; + }}); + +__PACKAGE__->register_method({ + name => 'exec-status', + path => 'exec-status', + method => 'GET', + protected => 1, + proxyto => 'node', + description => "Gets the status of the given pid started by the guest-agent", + 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 }), + pid => { + type => 'integer', + description => 'The PID to query' + }, + }, + }, + returns => { + type => 'object', + properties => { + exited => { + type => 'boolean', + description => 'Tells if the given command has exited yet.', + }, + exitcode => { + type => 'integer', + optional => 1, + description => 'process exit code if it was normally terminated.', + }, + signal=> { + type => 'integer', + optional => 1, + description => 'signal number or exception code if the process was abnormally terminated.', + }, + 'out-data' => { + type => 'string', + optional => 1, + description => 'stdout of the process', + }, + 'err-data' => { + type => 'string', + optional => 1, + description => 'stderr of the process', + }, + 'out-truncated' => { + type => 'boolean', + optional => 1, + description => 'true if stdout was not fully captured', + }, + 'err-truncated' => { + type => 'boolean', + optional => 1, + description => 'true if stderr was not fully captured', + }, + }, + }, + code => sub { + my ($param) = @_; + + my $vmid = $param->{vmid}; + my $pid = int($param->{pid}); + + my $res = PVE::QemuServer::Agent::qemu_exec_status($vmid, $pid); + + return $res; + }}); + 1; diff --git a/PVE/QemuServer/Agent.pm b/PVE/QemuServer/Agent.pm index 8fad1091..dcd43df2 100644 --- a/PVE/QemuServer/Agent.pm +++ b/PVE/QemuServer/Agent.pm @@ -3,6 +3,8 @@ package PVE::QemuServer::Agent; use strict; use warnings; use PVE::QemuServer; +use MIME::Base64 qw(decode_base64); +use JSON; use base 'Exporter'; our @EXPORT_OK = qw( @@ -62,4 +64,52 @@ sub agent_cmd { return $res; } +sub qemu_exec { + my ($vmid, $cmd) = @_; + + + my $path = shift @$cmd; + my $arguments = $cmd; + + my $args = { + path => $path, + arg => $arguments, + 'capture-output' => JSON::true, + }; + my $res = agent_cmd($vmid, "exec", $args, "can't execute command '$path $arguments'"); + + return $res; +} + +sub qemu_exec_status { + my ($vmid, $pid) = @_; + + my $res = agent_cmd($vmid, "exec-status", { pid => $pid }, "can't get exec status for '$pid'"); + + if ($res->{'out-data'}) { + my $decoded = eval { decode_base64($res->{'out-data'}) }; + warn $@ if $@; + if (defined($decoded)) { + $res->{'out-data'} = $decoded; + } + } + + if ($res->{'err-data'}) { + my $decoded = eval { decode_base64($res->{'err-data'}) }; + warn $@ if $@; + if (defined($decoded)) { + $res->{'err-data'} = $decoded; + } + } + + # convert JSON::Boolean to 1/0 + foreach my $d (keys %$res) { + if (JSON::is_bool($res->{$d})) { + $res->{$d} = ($res->{$d})? 1 : 0; + } + } + + return $res; +} + 1;