]> git.proxmox.com Git - pve-manager.git/commitdiff
pvesh cleanup: use a handler class - PVE/CLI/pvesh.pm
authorDietmar Maurer <dietmar@proxmox.com>
Thu, 26 Jul 2018 13:20:15 +0000 (15:20 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Thu, 26 Jul 2018 13:20:15 +0000 (15:20 +0200)
PVE/CLI/pvesh.pm [new file with mode: 0755]
bin/Makefile
bin/pvesh [changed mode: 0755->0644]

diff --git a/PVE/CLI/pvesh.pm b/PVE/CLI/pvesh.pm
new file mode 100755 (executable)
index 0000000..6808e77
--- /dev/null
@@ -0,0 +1,505 @@
+package PVE::CLI::pvesh;
+
+use strict;
+use warnings;
+use HTTP::Status qw(:constants :is status_message);
+use String::ShellQuote;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::SafeSyslog;
+use PVE::Cluster;
+use PVE::INotify;
+use PVE::RPCEnvironment;
+use PVE::RESTHandler;
+use PVE::CLIFormatter;
+use PVE::CLIHandler;
+use PVE::API2Tools;
+use PVE::API2;
+use JSON;
+
+use base qw(PVE::CLIHandler);
+
+my $disable_proxy = 0;
+my $opt_nooutput = 0;
+
+# compatibility code
+my $optmatch;
+do {
+    $optmatch = 0;
+    if ($ARGV[0]) {
+       if ($ARGV[0] eq '--noproxy') {
+           shift @ARGV;
+           $disable_proxy = 1;
+           $optmatch = 1;
+       } elsif ($ARGV[0] eq '--nooutput') {
+           # we use this when starting task in CLI (suppress printing upid)
+           # for example 'pvesh --nooutput create /nodes/localhost/stopall'
+           shift @ARGV;
+           $opt_nooutput = 1;
+           $optmatch = 1;
+       }
+   }
+} while ($optmatch);
+
+sub setup_environment {
+    PVE::RPCEnvironment->setup_default_cli_env();
+}
+
+sub complete_api_path {
+    my($text) = @_;
+
+    my ($dir, undef, $rest) = $text =~ m|^(.*/)?(([^/]*))?$|;
+
+    my $path = $dir // ''; # copy
+
+    $path =~ s|/+|/|g;
+    $path =~ s|^\/||;
+    $path =~ s|\/$||;
+
+    my $res = [];
+
+    my $di = dir_info($path);
+    if (my $children = $di->{children}) {
+       foreach my $c (@$children) {
+           if ($c =~ /^\Q$rest/) {
+               my $new =  $dir ? "$dir$c" : $c;
+               push @$res, $new;
+           }
+       }
+    }
+
+    if (scalar(@$res) == 1) {
+       return [$res->[0], "$res->[0]/"];
+    }
+
+    return $res;
+}
+
+my $method_map = {
+    create => 'POST',
+    set => 'PUT',
+    get => 'GET',
+    delete => 'DELETE',
+};
+
+sub check_proxyto {
+    my ($info, $uri_param) = @_;
+
+    my $rpcenv = PVE::RPCEnvironment->get();
+
+    if ($info->{proxyto} || $info->{proxyto_callback}) {
+       my $node = PVE::API2Tools::resolve_proxyto(
+           $rpcenv, $info->{proxyto_callback}, $info->{proxyto}, $uri_param);
+
+       if ($node ne 'localhost' && ($node ne PVE::INotify::nodename())) {
+           die "proxy loop detected - aborting\n" if $disable_proxy;
+           my $remip = PVE::Cluster::remote_node_ip($node);
+           return ($node, $remip);
+       }
+    }
+
+    return undef;
+}
+
+sub proxy_handler {
+    my ($node, $remip, $path, $cmd, $param) = @_;
+
+    my $args = [];
+    foreach my $key (keys %$param) {
+       next if $key eq 'quiet' || $key eq 'output-format'; # just to  be sure
+       push @$args, "--$key", $param->{$key};
+    }
+
+    my $remcmd = ['ssh', '-o', 'BatchMode=yes', "root\@$remip",
+                 'pvesh', '--noproxy', $cmd, $path,
+                 '--output-format', 'json'];
+
+    if (scalar(@$args)) {
+       my $cmdargs = [String::ShellQuote::shell_quote(@$args)];
+       push @$remcmd, @$cmdargs;
+    }
+
+    my $json = '';
+    PVE::Tools::run_command($remcmd, errmsg => "proxy handler failed",
+                           outfunc => sub { $json .= shift });
+
+    return decode_json($json);
+}
+
+sub extract_children {
+    my ($lnk, $data) = @_;
+
+    my $res = [];
+
+    return $res if !($lnk && $data);
+
+    my $href = $lnk->{href};
+    if ($href =~ m/^\{(\S+)\}$/) {
+       my $prop = $1;
+
+       foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @$data) {
+           next if !ref($elem);
+           my $value = $elem->{$prop};
+           push @$res, $value;
+       }
+    }
+
+    return $res;
+}
+
+sub dir_info {
+    my ($path) = @_;
+
+    my $res = { path => $path };
+    my $uri_param = {};
+    my ($handler, $info, $pm) = PVE::API2->find_handler('GET', $path, $uri_param);
+    if ($handler && $info) {
+       eval {
+           my $data = $handler->handle($info, $uri_param);
+           my $lnk = PVE::JSONSchema::method_get_child_link($info);
+           $res->{children} = extract_children($lnk, $data);
+       }; # ignore errors ?
+    }
+    return $res;
+}
+
+sub resource_cap {
+    my ($path) = @_;
+
+    my $res = '';
+
+    my ($handler, $info) = PVE::API2->find_handler('GET', $path);
+    if (!($handler && $info)) {
+       $res .= '--';
+    } else {
+       if (PVE::JSONSchema::method_get_child_link($info)) {
+           $res .= 'Dr';
+       } else {
+           $res .= '-r';
+       }
+    }
+
+    ($handler, $info) = PVE::API2->find_handler('PUT', $path);
+    if (!($handler && $info)) {
+       $res .= '-';
+    } else {
+       $res .= 'w';
+    }
+
+    ($handler, $info) = PVE::API2->find_handler('POST', $path);
+    if (!($handler && $info)) {
+       $res .= '-';
+    } else {
+       $res .= 'c';
+    }
+
+    ($handler, $info) = PVE::API2->find_handler('DELETE', $path);
+    if (!($handler && $info)) {
+       $res .= '-';
+    } else {
+       $res .= 'd';
+    }
+
+    return $res;
+}
+
+# dynamically update schema definition
+# like: pvesh <get|set|create|delete|help> <path>
+
+sub extract_path_info {
+    my ($uri_param) = @_;
+
+    my $info;
+
+    my $test_path_properties = sub {
+       my ($method, $path) = @_;
+       (undef, $info) = PVE::API2->find_handler($method, $path, $uri_param);
+    };
+
+    if (defined(my $cmd = $ARGV[0])) {
+       if (my $method = $method_map->{$cmd}) {
+           if (my $path = $ARGV[1]) {
+               $test_path_properties->($method, $path);
+           }
+       } elsif ($cmd eq 'bashcomplete') {
+           my $cmdline = substr($ENV{COMP_LINE}, 0, $ENV{COMP_POINT});
+           my $args = PVE::Tools::split_args($cmdline);
+           if (defined(my $cmd = $args->[1])) {
+               if (my $method = $method_map->{$cmd}) {
+                   if (my $path = $args->[2]) {
+                       $test_path_properties->($method, $path);
+                   }
+               }
+           }
+       }
+    }
+
+    return $info;
+}
+
+
+my $path_properties = {};
+
+my $api_path_property = {
+    description => "API path.",
+    type => 'string',
+    completion => sub {
+       my ($cmd, $pname, $cur, $args) = @_;
+       return complete_api_path($cur);
+    },
+};
+
+my $uri_param = {};
+if (my $info = 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_properties->{api_path} = $api_path_property;
+$path_properties->{noproxy} = {
+    description => "Disable automatic proxying.",
+    type => 'boolean',
+    optional => 1,
+};
+
+sub call_api_method {
+    my ($cmd, $param) = @_;
+
+    my $method = $method_map->{$cmd} || die "unable to map command '$cmd'";
+
+    my $path = PVE::Tools::extract_param($param, 'api_path');
+    die "missing API path\n" if !defined($path);
+
+    my $stdopts =  PVE::RESTHandler::extract_standard_output_properties($param);
+
+    $opt_nooutput = 1 if $stdopts->{quiet};
+
+    my $uri_param = {};
+    my ($handler, $info) = PVE::API2->find_handler($method, $path, $uri_param);
+    if (!$handler || !$info) {
+       die "no '$cmd' handler for '$path'\n";
+    }
+
+    my $data;
+    my ($node, $remip) = check_proxyto($info, $uri_param);
+    if ($node) {
+       $data = proxy_handler($node, $remip, $path, $cmd, $param);
+    } else {
+       foreach my $p (keys %$uri_param) {
+           $param->{$p} = $uri_param->{$p};
+       }
+
+       $data = $handler->handle($info, $param);
+    }
+
+    return if $opt_nooutput || $stdopts->{quiet};
+
+    PVE::CLIFormatter::print_api_result($data, $info->{returns}, undef, $stdopts);
+}
+
+__PACKAGE__->register_method ({
+    name => 'ls',
+    path => 'ls',
+    method => 'GET',
+    description => "List child objects on <api_path>.",
+    parameters => {
+       additionalProperties => 0,
+       properties => PVE::RESTHandler::add_standard_output_properties($path_properties),
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       my $path = PVE::Tools::extract_param($param, 'api_path');
+
+       my $stdopts =  PVE::RESTHandler::extract_standard_output_properties($param);
+
+       my $uri_param = {};
+       my ($handler, $info) = PVE::API2->find_handler('GET', $path, $uri_param);
+       if (!$handler || !$info) {
+           die "no such resource '$path'\n";
+       }
+
+       my $link = PVE::JSONSchema::method_get_child_link($info);
+       die "resource '$path' does not define child links\n" if !$link;
+
+       my $res;
+
+       my ($node, $remip) = check_proxyto($info, $uri_param);
+       if ($node) {
+           $res = proxy_handler($node, $remip, $path, 'ls', $param);
+       } else {
+           foreach my $p (keys %$uri_param) {
+               $param->{$p} = $uri_param->{$p};
+           }
+
+           my $data = $handler->handle($info, $param);
+
+           my $children = extract_children($link, $data);
+
+           $res = [];
+           foreach my $c (@$children) {
+               my $item = { name => $c, capabilities => resource_cap("$path/$c")};
+               push @$res, $item;
+           }
+       }
+
+       my $schema = { type => 'array', items => { type => 'object' }};
+       $stdopts->{sort_key} = 'name';
+       $stdopts->{noborder} //= 1;
+       $stdopts->{noheader} //= 1;
+       PVE::CLIFormatter::print_api_result($res, $schema, ['capabilities', 'name'], $stdopts);
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'get',
+    path => 'get',
+    method => 'GET',
+    description => "Call API GET on <api_path>.",
+    parameters => {
+       additionalProperties => 0,
+       properties => PVE::RESTHandler::add_standard_output_properties($path_properties),
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       call_api_method('get', $param);
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'set',
+    path => 'set',
+    method => 'PUT',
+    description => "Call API PUT on <api_path>.",
+    parameters => {
+       additionalProperties => 0,
+       properties => $path_properties,
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       call_api_method('set', $param);
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'create',
+    path => 'create',
+    method => 'POST',
+    description => "Call API POST on <api_path>.",
+    parameters => {
+       additionalProperties => 0,
+       properties => $path_properties,
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       call_api_method('create', $param);
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'delete',
+    path => 'delete',
+    method => 'DELETE',
+    description => "Call API DELETE on <api_path>.",
+    parameters => {
+       additionalProperties => 0,
+       properties => $path_properties,
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       call_api_method('delete', $param);
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'usage',
+    path => 'usage',
+    method => 'GET',
+    description => "print API usage information for <api_path>.",
+    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 %$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 = $method_map->{$cmd};
+           my $uri_param = {};
+           my ($handler, $info) = PVE::API2->find_handler($method, $path, $uri_param);
+           next if !$handler;
+           $found = 1;
+
+           if ($param->{verbose}) {
+               print $handler->usage_str(
+                   $info->{name}, "pvesh $cmd $path", undef, $uri_param, 'full');
+
+           } else {
+               print "USAGE: " . $handler->usage_str(
+                   $info->{name}, "pvesh $cmd $path", 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 = {
+    usage => [ __PACKAGE__, 'usage', ['api_path']],
+    get => [ __PACKAGE__, 'get', ['api_path']],
+    ls => [ __PACKAGE__, 'ls', ['api_path']],
+    set => [ __PACKAGE__, 'set', ['api_path']],
+    create => [ __PACKAGE__, 'create', ['api_path']],
+    delete => [ __PACKAGE__, 'delete', ['api_path']],
+};
+
+1;
index ddc3915bd215c91003680496ae70d97eefcfc0b4..9f4906437e3cdac3a4a227a57702922b7e65e01e 100644 (file)
@@ -5,12 +5,11 @@ export NOVIEW=1
 include /usr/share/pve-doc-generator/pve-doc-generator.mk
 
 SERVICES = pvestatd pveproxy pvedaemon spiceproxy
-CLITOOLS = vzdump pvesubscription pveceph pveam pvesr pvenode
+CLITOOLS = vzdump pvesubscription pveceph pveam pvesr pvenode pvesh
 
 SCRIPTS =                      \
        ${SERVICES}             \
        ${CLITOOLS}             \
-       pvesh                   \
        pvebanner               \
        pveversion              \
        pvemailforward.pl       \
@@ -26,7 +25,6 @@ CLI_MANS =                            \
        pveversion.1                    \
        pveupgrade.1                    \
        pveperf.1                       \
-       pvesh.1                         \
        pvereport.1                     \
 
 
@@ -43,7 +41,6 @@ all: ${SERVICE_MANS} ${CLI_MANS} pvemailforward
 
 pveversion.1.pod: pveversion
 pveupgrade.1.pod: pveupgrade
-pvesh.1.pod: pvesh
 pvereport.1.pod: pvereport
 
 %.service-bash-completion:
old mode 100755 (executable)
new mode 100644 (file)
index a024576..99f59bc
--- a/bin/pvesh
+++ b/bin/pvesh
@@ -1,542 +1,8 @@
 #!/usr/bin/perl
 
-package pvesh;
-
 use strict;
 use warnings;
-use HTTP::Status qw(:constants :is status_message);
-use String::ShellQuote;
-use PVE::JSONSchema qw(get_standard_option);
-use PVE::SafeSyslog;
-use PVE::Cluster;
-use PVE::INotify;
-use PVE::RPCEnvironment;
-use PVE::RESTHandler;
-use PVE::CLIFormatter;
-use PVE::CLIHandler;
-use PVE::API2Tools;
-use PVE::API2;
-use JSON;
-
-use base qw(PVE::CLIHandler);
-
-my $disable_proxy = 0;
-my $opt_nooutput = 0;
-
-# compatibility code
-my $optmatch;
-do {
-    $optmatch = 0;
-    if ($ARGV[0]) {
-       if ($ARGV[0] eq '--noproxy') {
-           shift @ARGV;
-           $disable_proxy = 1;
-           $optmatch = 1;
-       } elsif ($ARGV[0] eq '--nooutput') {
-           # we use this when starting task in CLI (suppress printing upid)
-           # for example 'pvesh --nooutput create /nodes/localhost/stopall'
-           shift @ARGV;
-           $opt_nooutput = 1;
-           $optmatch = 1;
-       }
-   }
-} while ($optmatch);
-
-sub setup_environment {
-    PVE::RPCEnvironment->setup_default_cli_env();
-}
-
-sub complete_api_path {
-    my($text) = @_;
-
-    my ($dir, undef, $rest) = $text =~ m|^(.*/)?(([^/]*))?$|;
-
-    my $path = $dir // ''; # copy
-
-    $path =~ s|/+|/|g;
-    $path =~ s|^\/||;
-    $path =~ s|\/$||;
-
-    my $res = [];
-
-    my $di = dir_info($path);
-    if (my $children = $di->{children}) {
-       foreach my $c (@$children) {
-           if ($c =~ /^\Q$rest/) {
-               my $new =  $dir ? "$dir$c" : $c;
-               push @$res, $new;
-           }
-       }
-    }
-
-    if (scalar(@$res) == 1) {
-       return [$res->[0], "$res->[0]/"];
-    }
-
-    return $res;
-}
-
-my $method_map = {
-    create => 'POST',
-    set => 'PUT',
-    get => 'GET',
-    delete => 'DELETE',
-};
-
-sub check_proxyto {
-    my ($info, $uri_param) = @_;
-
-    my $rpcenv = PVE::RPCEnvironment->get();
-
-    if ($info->{proxyto} || $info->{proxyto_callback}) {
-       my $node = PVE::API2Tools::resolve_proxyto(
-           $rpcenv, $info->{proxyto_callback}, $info->{proxyto}, $uri_param);
-
-       if ($node ne 'localhost' && ($node ne PVE::INotify::nodename())) {
-           die "proxy loop detected - aborting\n" if $disable_proxy;
-           my $remip = PVE::Cluster::remote_node_ip($node);
-           return ($node, $remip);
-       }
-    }
-
-    return undef;
-}
-
-sub proxy_handler {
-    my ($node, $remip, $path, $cmd, $param) = @_;
-
-    my $args = [];
-    foreach my $key (keys %$param) {
-       next if $key eq 'quiet' || $key eq 'output-format'; # just to  be sure
-       push @$args, "--$key", $param->{$key};
-    }
-
-    my $remcmd = ['ssh', '-o', 'BatchMode=yes', "root\@$remip",
-                 'pvesh', '--noproxy', $cmd, $path,
-                 '--output-format', 'json'];
-
-    if (scalar(@$args)) {
-       my $cmdargs = [String::ShellQuote::shell_quote(@$args)];
-       push @$remcmd, @$cmdargs;
-    }
-
-    my $json = '';
-    PVE::Tools::run_command($remcmd, errmsg => "proxy handler failed",
-                           outfunc => sub { $json .= shift });
-
-    return decode_json($json);
-}
-
-sub extract_children {
-    my ($lnk, $data) = @_;
-
-    my $res = [];
-
-    return $res if !($lnk && $data);
-
-    my $href = $lnk->{href};
-    if ($href =~ m/^\{(\S+)\}$/) {
-       my $prop = $1;
-
-       foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @$data) {
-           next if !ref($elem);
-           my $value = $elem->{$prop};
-           push @$res, $value;
-       }
-    }
-
-    return $res;
-}
-
-sub dir_info {
-    my ($path) = @_;
-
-    my $res = { path => $path };
-    my $uri_param = {};
-    my ($handler, $info, $pm) = PVE::API2->find_handler('GET', $path, $uri_param);
-    if ($handler && $info) {
-       eval {
-           my $data = $handler->handle($info, $uri_param);
-           my $lnk = PVE::JSONSchema::method_get_child_link($info);
-           $res->{children} = extract_children($lnk, $data);
-       }; # ignore errors ?
-    }
-    return $res;
-}
-
-sub resource_cap {
-    my ($path) = @_;
-
-    my $res = '';
-
-    my ($handler, $info) = PVE::API2->find_handler('GET', $path);
-    if (!($handler && $info)) {
-       $res .= '--';
-    } else {
-       if (PVE::JSONSchema::method_get_child_link($info)) {
-           $res .= 'Dr';
-       } else {
-           $res .= '-r';
-       }
-    }
-
-    ($handler, $info) = PVE::API2->find_handler('PUT', $path);
-    if (!($handler && $info)) {
-       $res .= '-';
-    } else {
-       $res .= 'w';
-    }
-
-    ($handler, $info) = PVE::API2->find_handler('POST', $path);
-    if (!($handler && $info)) {
-       $res .= '-';
-    } else {
-       $res .= 'c';
-    }
-
-    ($handler, $info) = PVE::API2->find_handler('DELETE', $path);
-    if (!($handler && $info)) {
-       $res .= '-';
-    } else {
-       $res .= 'd';
-    }
-
-    return $res;
-}
-
-# dynamically update schema definition
-# like: pvesh <get|set|create|delete|help> <path>
-
-sub extract_path_info {
-    my ($uri_param) = @_;
-
-    my $info;
-
-    my $test_path_properties = sub {
-       my ($method, $path) = @_;
-       (undef, $info) = PVE::API2->find_handler($method, $path, $uri_param);
-    };
-
-    if (defined(my $cmd = $ARGV[0])) {
-       if (my $method = $method_map->{$cmd}) {
-           if (my $path = $ARGV[1]) {
-               $test_path_properties->($method, $path);
-           }
-       } elsif ($cmd eq 'bashcomplete') {
-           my $cmdline = substr($ENV{COMP_LINE}, 0, $ENV{COMP_POINT});
-           my $args = PVE::Tools::split_args($cmdline);
-           if (defined(my $cmd = $args->[1])) {
-               if (my $method = $method_map->{$cmd}) {
-                   if (my $path = $args->[2]) {
-                       $test_path_properties->($method, $path);
-                   }
-               }
-           }
-       }
-    }
-
-    return $info;
-}
-
-
-my $path_properties = {};
-
-my $api_path_property = {
-    description => "API path.",
-    type => 'string',
-    completion => sub {
-       my ($cmd, $pname, $cur, $args) = @_;
-       return complete_api_path($cur);
-    },
-};
-
-my $uri_param = {};
-if (my $info = 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_properties->{api_path} = $api_path_property;
-$path_properties->{noproxy} = {
-    description => "Disable automatic proxying.",
-    type => 'boolean',
-    optional => 1,
-};
-
-sub call_api_method {
-    my ($cmd, $param) = @_;
-
-    my $method = $method_map->{$cmd} || die "unable to map command '$cmd'";
-
-    my $path = PVE::Tools::extract_param($param, 'api_path');
-    die "missing API path\n" if !defined($path);
-
-    my $stdopts =  PVE::RESTHandler::extract_standard_output_properties($param);
-
-    $opt_nooutput = 1 if $stdopts->{quiet};
-
-    my $uri_param = {};
-    my ($handler, $info) = PVE::API2->find_handler($method, $path, $uri_param);
-    if (!$handler || !$info) {
-       die "no '$cmd' handler for '$path'\n";
-    }
-
-    my $data;
-    my ($node, $remip) = check_proxyto($info, $uri_param);
-    if ($node) {
-       $data = proxy_handler($node, $remip, $path, $cmd, $param);
-    } else {
-       foreach my $p (keys %$uri_param) {
-           $param->{$p} = $uri_param->{$p};
-       }
-
-       $data = $handler->handle($info, $param);
-    }
-
-    return if $opt_nooutput || $stdopts->{quiet};
-
-    PVE::CLIFormatter::print_api_result($data, $info->{returns}, undef, $stdopts);
-}
-
-__PACKAGE__->register_method ({
-    name => 'ls',
-    path => 'ls',
-    method => 'GET',
-    description => "List child objects on <api_path>.",
-    parameters => {
-       additionalProperties => 0,
-       properties => PVE::RESTHandler::add_standard_output_properties($path_properties),
-    },
-    returns => { type => 'null' },
-    code => sub {
-       my ($param) = @_;
-
-       my $path = PVE::Tools::extract_param($param, 'api_path');
-
-       my $stdopts =  PVE::RESTHandler::extract_standard_output_properties($param);
-
-       my $uri_param = {};
-       my ($handler, $info) = PVE::API2->find_handler('GET', $path, $uri_param);
-       if (!$handler || !$info) {
-           die "no such resource '$path'\n";
-       }
-
-       my $link = PVE::JSONSchema::method_get_child_link($info);
-       die "resource '$path' does not define child links\n" if !$link;
-
-       my $res;
-
-       my ($node, $remip) = check_proxyto($info, $uri_param);
-       if ($node) {
-           $res = proxy_handler($node, $remip, $path, 'ls', $param);
-       } else {
-           foreach my $p (keys %$uri_param) {
-               $param->{$p} = $uri_param->{$p};
-           }
-
-           my $data = $handler->handle($info, $param);
-
-           my $children = extract_children($link, $data);
-
-           $res = [];
-           foreach my $c (@$children) {
-               my $item = { name => $c, capabilities => resource_cap("$path/$c")};
-               push @$res, $item;
-           }
-       }
-
-       my $schema = { type => 'array', items => { type => 'object' }};
-       $stdopts->{sort_key} = 'name';
-       $stdopts->{noborder} //= 1;
-       $stdopts->{noheader} //= 1;
-       PVE::CLIFormatter::print_api_result($res, $schema, ['capabilities', 'name'], $stdopts);
-
-       return undef;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'get',
-    path => 'get',
-    method => 'GET',
-    description => "Call API GET on <api_path>.",
-    parameters => {
-       additionalProperties => 0,
-       properties => PVE::RESTHandler::add_standard_output_properties($path_properties),
-    },
-    returns => { type => 'null' },
-    code => sub {
-       my ($param) = @_;
-
-       call_api_method('get', $param);
-
-       return undef;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'set',
-    path => 'set',
-    method => 'PUT',
-    description => "Call API PUT on <api_path>.",
-    parameters => {
-       additionalProperties => 0,
-       properties => $path_properties,
-    },
-    returns => { type => 'null' },
-    code => sub {
-       my ($param) = @_;
-
-       call_api_method('set', $param);
-
-       return undef;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'create',
-    path => 'create',
-    method => 'POST',
-    description => "Call API POST on <api_path>.",
-    parameters => {
-       additionalProperties => 0,
-       properties => $path_properties,
-    },
-    returns => { type => 'null' },
-    code => sub {
-       my ($param) = @_;
-
-       call_api_method('create', $param);
-
-       return undef;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'delete',
-    path => 'delete',
-    method => 'DELETE',
-    description => "Call API DELETE on <api_path>.",
-    parameters => {
-       additionalProperties => 0,
-       properties => $path_properties,
-    },
-    returns => { type => 'null' },
-    code => sub {
-       my ($param) = @_;
-
-       call_api_method('delete', $param);
-
-       return undef;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'usage',
-    path => 'usage',
-    method => 'GET',
-    description => "print API usage information for <api_path>.",
-    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 %$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 = $method_map->{$cmd};
-           my $uri_param = {};
-           my ($handler, $info) = PVE::API2->find_handler($method, $path, $uri_param);
-           next if !$handler;
-           $found = 1;
-
-           if ($param->{verbose}) {
-               print $handler->usage_str(
-                   $info->{name}, "pvesh $cmd $path", undef, $uri_param, 'full');
-
-           } else {
-               print "USAGE: " . $handler->usage_str(
-                   $info->{name}, "pvesh $cmd $path", 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 = {
-    usage => [ __PACKAGE__, 'usage', ['api_path']],
-    get => [ __PACKAGE__, 'get', ['api_path']],
-    ls => [ __PACKAGE__, 'ls', ['api_path']],
-    set => [ __PACKAGE__, 'set', ['api_path']],
-    create => [ __PACKAGE__, 'create', ['api_path']],
-    delete => [ __PACKAGE__, 'delete', ['api_path']],
-};
-
-my $cmd = $ARGV[0];
-
-__PACKAGE__->run_cli_handler();
-
-
-__END__
-
-=head1 NAME
-
-pvesh - shell interface to the Promox VE API
-
-=head1 SYNOPSIS
-
-pvesh [get|set|create|delete|usage] [REST API path] [--verbose]
-
-=head1 DESCRIPTION
-
-pvesh provides a command line interface to the Proxmox VE REST API.
-
-=head1 EXAMPLES
-
-get the list of nodes in my cluster
-
-pvesh get /nodes
-
-get a list of available options for the datacenter
-
-pvesh usage cluster/options -v
-
-set the HTMl5 NoVNC console as the default console for the datacenter
-
-pvesh set cluster/options -console html5
 
-=head1 SEE ALSO
+use PVE::CLI::pvesh;
 
-qm(1), pct(1)
+PVE::CLI::pvesh->run_cli_handler();