#!/usr/bin/perl package PVE::CLI::pveclient; use strict; use warnings; use Cwd 'abs_path'; use Data::Dumper; use PVE::APIClient::JSONSchema qw(register_standard_option get_standard_option); use PVE::APIClient::CLIFormatter; use PVE::APIClient::CLIHandler; use PVE::APIClient::PTY; use PVE::APIClient::LWP; use PVE::APIClient::Helpers; use PVE::APIClient::Config; use PVE::APIClient::Commands::config; use PVE::APIClient::Commands::remote; use PVE::APIClient::Commands::list; use PVE::APIClient::Commands::lxc; use PVE::APIClient::Commands::GuestStatus; use JSON; sub call_api_method { my ($method, $param, $options) = @_; my $path = PVE::APIClient::Tools::extract_param($param, 'api_path'); die "missing API path\n" if !defined($path); my $remote = PVE::APIClient::Tools::extract_param($param, 'remote'); die "missing remote\n" if !defined($remote); my $config = PVE::APIClient::Config->load(); my $uri_param = {}; my $info = PVE::APIClient::Helpers::find_method_info($path, $method, $uri_param); my $conn = PVE::APIClient::Config->remote_conn($config, $remote); my $res = $conn->call($method, "api2/json/$path", $param); die "undefined result" if !defined($res); die "undefined result data" if !exists($res->{data}); return $res->{data}; } use base qw(PVE::APIClient::CLIHandler); sub read_password { return PVE::APIClient::PTY::read_password("Remote password: ") } my $cmd = $ARGV[0]; if ($cmd && $cmd eq 'packagedepends') { # experimental code to print required perl packages my $packages = {}; my $dir = Cwd::getcwd; foreach my $k (keys %INC) { my $file = abs_path($INC{$k}); next if $file =~ m/^\Q$dir\E/; my $res = `dpkg -S '$file'`; if ($res && $res =~ m/^(\S+): $file$/) { my $debian_package = $1; $debian_package =~ s/:amd64$//; $packages->{$debian_package} = 1; } else { die "unable to find package for '$file'\n"; } } print join("\n", sort(keys %$packages)) . "\n"; exit(0); } my $path_properties = {}; my $path_returns = { type => 'null' }; my $api_path_property = { description => "API path.", type => 'string', completion => sub { my ($cmd, $pname, $cur, $args) = @_; return PVE::APIClient::Helpers::complete_api_path($cur); }, }; # dynamically update schema definition for direct API call # like: pveclient api my $uri_param = {}; if (my $info = PVE::APIClient::Helpers::extract_path_info($uri_param)) { foreach my $key (keys %{$info->{parameters}->{properties}}) { next if defined($uri_param->{$key}); $path_properties->{$key} = $info->{parameters}->{properties}->{$key}; } $path_returns = $info->{returns}; } $path_properties->{remote} = get_standard_option('pveclient-remote-name'); $path_properties->{api_path} = $api_path_property; my $format_result = sub { my ($data, $schema, $options) = @_; # NOTE; we need to use $path_returns instead of $schema PVE::APIClient::CLIFormatter::print_api_result($data, $path_returns, undef, $options); }; __PACKAGE__->register_method ({ name => 'pveclient_get', path => 'pveclient_get', method => 'GET', description => "Call API GET on .", parameters => { additionalProperties => 0, properties => $path_properties, }, returns => $path_returns, code => sub { my ($param, $options) = @_; return call_api_method('GET', $param, $options); }}); __PACKAGE__->register_method ({ name => 'pveclient_set', path => 'pveclient_set', method => 'PUT', description => "Call API PUT on .", parameters => { additionalProperties => 0, properties => $path_properties, }, returns => $path_returns, code => sub { my ($param, $options) = @_; return call_api_method('PUT', $param, $options); }}); __PACKAGE__->register_method ({ name => 'pveclient_create', path => 'pveclient_create', method => 'POST', description => "Call API POST on .", parameters => { additionalProperties => 0, properties => $path_properties, }, returns => $path_returns, code => sub { my ($param, $options) = @_; return call_api_method('PUSH', $param, $options); }}); __PACKAGE__->register_method ({ name => 'pveclient_delete', path => 'pveclient_delete', method => 'DELETE', description => "Call API DELETE on .", parameters => { additionalProperties => 0, properties => $path_properties, }, returns => $path_returns, code => sub { my ($param, $options) = @_; return call_api_method('DELETE', $param, $options); }}); __PACKAGE__->register_method ({ name => 'pveclient_usage', path => 'pveclient_usage', method => 'GET', description => "print API usage information for .", parameters => { additionalProperties => 0, properties => { api_path => $api_path_property, verbose => { description => "Verbose output format.", type => 'boolean', optional => 1, }, returns => { description => "Including schema for returned data.", type => 'boolean', optional => 1, }, command => { description => "API command.", type => 'string', enum => [ keys %$PVE::APIClient::Helpers::method_map ], optional => 1, }, }, }, returns => { type => 'null' }, code => sub { my ($param) = @_; my $path = $param->{api_path}; my $found = 0; foreach my $cmd (qw(get set create delete)) { next if $param->{command} && $cmd ne $param->{command}; my $method = $PVE::APIClient::Helpers::method_map->{$cmd}; my $uri_param = {}; my $info = PVE::APIClient::Helpers::find_method_info($path, $method, $uri_param, 1); next if !$info; $found = 1; my $prefix = "pveclient api $cmd $path"; if ($param->{verbose}) { print PVE::APIClient::RESTHandler::getopt_usage( $info, $prefix, undef, $uri_param, 'full'); } else { print "USAGE: " . PVE::APIClient::RESTHandler::getopt_usage( $info, $prefix, undef, $uri_param, 'short'); } if ($param-> {returns}) { my $schema = to_json($info->{returns}, {utf8 => 1, canonical => 1, pretty => 1 }); print "RETURNS: $schema\n"; } } if (!$found) { if ($param->{command}) { die "no '$param->{command}' handler for '$path'\n"; } else { die "no such resource '$path'\n" } } return undef; }}); our $cmddef = { config => $PVE::APIClient::Commands::config::cmddef, list => $PVE::APIClient::Commands::list::cmddef, lxc => $PVE::APIClient::Commands::lxc::cmddef, remote => $PVE::APIClient::Commands::remote::cmddef, resume => [ 'PVE::APIClient::Commands::GuestStatus', 'resume', ['remote', 'vmid']], shutdown => [ 'PVE::APIClient::Commands::GuestStatus', 'shutdown', ['remote', 'vmid']], spice => [ 'PVE::APIClient::Commands::GuestStatus', 'spice', ['remote', 'vmid']], start => [ 'PVE::APIClient::Commands::GuestStatus', 'start', ['remote', 'vmid']], stop => [ 'PVE::APIClient::Commands::GuestStatus', 'stop', ['remote', 'vmid']], suspend => [ 'PVE::APIClient::Commands::GuestStatus', 'suspend', ['remote', 'vmid']], api => { usage => [ __PACKAGE__, 'pveclient_usage', ['api_path']], get => [ __PACKAGE__, 'pveclient_get', ['remote', 'api_path'], {}, $format_result ], set => [ __PACKAGE__, 'pveclient_set', ['remote', 'api_path'], {}, $format_result ], create => [ __PACKAGE__, 'pveclient_create', ['remote', 'api_path'], {}, $format_result ], delete => [ __PACKAGE__, 'pveclient_delete', ['remote', 'api_path'], {}, $format_result ], }, }; if ($cmd && $cmd eq 'printsynopsis') { print __PACKAGE__->generate_asciidoc_synopsis(); exit(0); } __PACKAGE__->run_cli_handler();