]> git.proxmox.com Git - pve-manager.git/commitdiff
pvesh: decode streamed responses
authorChristoph Heiss <c.heiss@proxmox.com>
Wed, 7 Jun 2023 14:15:11 +0000 (16:15 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Thu, 19 Oct 2023 08:08:54 +0000 (10:08 +0200)
This allows to use `pvesh` on endpoints like /nodes/{node}/journal,
which return streamed (and possibly gzip'd) responses.

Currently, e.g. `pvesh get /nodes/localhost/journal --lastentries 10`
fails with:

  gzip: stdout: Broken pipe
  got hash object, but result schema specified array!

Using e.g. `--output-format yaml` resulted in:

  ---
  download:
    content-encoding: gzip
    content-type: application/json
    fh: &1 !!perl/ref
      =: *1
    stream: 1

  gzip: stdout: Broken pipe
  Failed to write

This is due the API call returning a "download" object (as seen above),
which contains (among some other things) a file handle to read the
response from.

With this patch, the response from such endpoints is now correctly read
and displayed. Only handles combinations of `Content-Encoding` == 'gzip'
and either 'text/plain' or 'application/json' for `Content-Type`.

This tries to mimic the behavior of the API server implementation when
encountering `download` objects.

Tested this with all four output formats 'text', 'json', 'json-pretty'
and 'yaml', as well as "cross-node" in a local test cluster.

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
PVE/CLI/pvesh.pm

index 28e2518d5ceccfa859fd93bdaf4a9d75ce574b53..61b4464d63ea209a784581656d0d5d1c0b2ee7d4 100755 (executable)
@@ -15,6 +15,7 @@ use PVE::CLIHandler;
 use PVE::API2Tools;
 use PVE::API2;
 use JSON;
+use IO::Uncompress::Gunzip qw(gunzip);
 
 use base qw(PVE::CLIHandler);
 
@@ -283,6 +284,41 @@ my $cond_add_standard_output_properties = sub {
     return PVE::RESTHandler::add_standard_output_properties($props, $keys);
 };
 
+my $handle_streamed_response = sub {
+    my ($download) = @_;
+    my ($fh, $path, $encoding, $type) =
+       $download->@{'fh', 'path', 'content-encoding', 'content-type'};
+
+    die "{download} returned but neither fh nor path given\n"
+       if !defined($fh) && !defined($path);
+
+    die "unknown 'content-encoding' $encoding\n"
+       if defined($encoding) && $encoding ne 'gzip';
+
+    die "unknown 'content-type' $type\n"
+       if defined($type) && $type !~ qw!^(text/plain)|(application/json)$!;
+
+    if (defined($path)) {
+       open($fh, '<', $path)
+           or die "open stream path '$path' for reading failed: $!\n";
+    }
+
+    local $/;
+    my $data = <$fh>;
+
+    if (defined($encoding)) {
+       my $out;
+       gunzip(\$data => \$out);
+       $data = $out;
+    }
+
+    if (defined($type) && $type eq 'application/json') {
+       $data = decode_json($data)->{data};
+    }
+
+    return $data;
+};
+
 sub call_api_method {
     my ($cmd, $param) = @_;
 
@@ -312,6 +348,9 @@ sub call_api_method {
        }
 
        $data = $handler->handle($info, $param);
+
+       $data = &$handle_streamed_response($data->{download})
+           if ref($data) eq 'HASH' && ref($data->{download}) eq 'HASH';
     }
 
     return if $opt_nooutput || $stdopts->{quiet};