X-Git-Url: https://git.proxmox.com/?p=pve-client.git;a=blobdiff_plain;f=PVE%2FAPIClient%2FHelpers.pm;h=9ee00e9d127bcdc17308bc4964141adb10430840;hp=c4bc2c80493d9e92304b9eb6cbc759f8f3f47cfc;hb=2ecf9b57e894b6308daee2e60a2d7f5bd417932c;hpb=df89fcb90718eef859af92d85382dcb8ea4287df diff --git a/PVE/APIClient/Helpers.pm b/PVE/APIClient/Helpers.pm index c4bc2c8..9ee00e9 100644 --- a/PVE/APIClient/Helpers.pm +++ b/PVE/APIClient/Helpers.pm @@ -7,13 +7,13 @@ use Storable; use JSON; use File::Path qw(make_path); +use PVE::APIClient::JSONSchema; use PVE::APIClient::Exception qw(raise); use Encode::Locale; use Encode; use HTTP::Status qw(:constants); my $pve_api_definition; -my $pve_api_path_hash; my $pve_api_definition_fn = "/usr/share/pve-client/pve-api-definition.dat"; @@ -24,27 +24,6 @@ my $method_map = { delete => 'DELETE', }; -my $build_pve_api_path_hash; -$build_pve_api_path_hash = sub { - my ($tree) = @_; - - my $class = ref($tree); - return $tree if !$class; - - if ($class eq 'ARRAY') { - foreach my $el (@$tree) { - $build_pve_api_path_hash->($el); - } - } elsif ($class eq 'HASH') { - if (defined($tree->{leaf}) && defined(my $path = $tree->{path})) { - $pve_api_path_hash->{$path} = $tree; - } - foreach my $k (keys %$tree) { - $build_pve_api_path_hash->($tree->{$k}); - } - } -}; - my $default_output_format = 'text'; my $client_output_format = $default_output_format; @@ -72,29 +51,71 @@ sub print_result { # TODO: implement different output formats ($format) if ($format eq 'json') { - print to_json($data, {utf8 => 1, allow_nonref => 1, canonical => 1, pretty => 1 }); + print to_json($data, {utf8 => 1, allow_nonref => 1, canonical => 1, pretty => 1 }); } elsif ($format eq 'text') { - my $type = $result_schema->{type}; - if ($type eq 'object') { - die "implement me"; - } elsif ($type eq 'array') { - my $item_type = $result_schema->{items}->{type}; - if ($item_type eq 'object') { - die "implement me"; - } elsif ($item_type eq 'array') { - die "implement me"; - } else { - foreach my $el (@$data) { - print "$el\n" + my $type = $result_schema->{type}; + if ($type eq 'object') { + die "implement me"; + } elsif ($type eq 'array') { + my $item_type = $result_schema->{items}->{type}; + if ($item_type eq 'object') { + die "implement me"; + } elsif ($item_type eq 'array') { + die "implement me"; + } else { + foreach my $el (@$data) { + print "$el\n" + } + } + } else { + print "$data\n"; + } + } else { + die "internal error: unknown output format"; # should not happen + } +} + +my $__real_remove_formats; $__real_remove_formats = sub { + my ($properties) = @_; + + foreach my $pname (keys %$properties) { + if (my $d = $properties->{$pname}) { + if (defined(my $format = $d->{format})) { + if (ref($format)) { + $__real_remove_formats->($format); + } elsif (!PVE::APIClient::JSONSchema::get_format($format)) { + # simply remove unknown format definitions + delete $d->{format}; } } - } else { - print "$data\n"; } - } else { - die "internal error: unknown output format"; # should not happen } -} +}; + +my $remove_unknown_formats; $remove_unknown_formats = sub { + my ($tree) = @_; + + my $class = ref($tree); + return if !$class; + + if ($class eq 'ARRAY') { + foreach my $el (@$tree) { + $remove_unknown_formats->($el); + } + } elsif ($class eq 'HASH') { + if (my $info = $tree->{info}) { + for my $method (qw(GET PUT PUSH DELETE)) { + next if !$info->{$method}; + my $properties = $info->{$method}->{parameters}->{properties}; + $__real_remove_formats->($properties) if $properties; + } + } + if ($tree->{children}) { + $remove_unknown_formats->($tree->{children}); + } + } + return; +}; sub get_api_definition { @@ -102,32 +123,56 @@ sub get_api_definition { open(my $fh, '<', $pve_api_definition_fn) || die "unable to open '$pve_api_definition_fn' - $!\n"; $pve_api_definition = Storable::fd_retrieve($fh); - $build_pve_api_path_hash->($pve_api_definition); + + $remove_unknown_formats->($pve_api_definition); } return $pve_api_definition; } -sub lookup_api_method { - my ($path, $method, $noerr) = @_; - - get_api_definition(); # make sure API data is loaded +my $map_path_to_info = sub { + my ($child_list, $stack, $uri_param) = @_; - my $info = $pve_api_path_hash->{$path}; + while (defined(my $comp = shift @$stack)) { + foreach my $child (@$child_list) { + my $text = $child->{text}; - if (!$info) { - return undef if $noerr; - die "unable to find API info for path '$path'\n"; + if ($text eq $comp) { + # found + } elsif ($text =~ m/^\{(\S+)\}$/) { + # found + $uri_param->{$1} = $comp; + } else { + next; # text next child + } + if ($child->{leaf} || !scalar(@$stack)) { + return $child; + } else { + $child_list = $child->{children}; + last; # test next path component + } + } } + return undef; +}; + +sub find_method_info { + my ($path, $method, $uri_param, $noerr) = @_; + + $uri_param //= {}; + + my $stack = [ grep { length($_) > 0 } split('\/+' , $path)]; # skip empty fragments - my $data = $info->{info}->{$method}; + my $api = get_api_definition(); - if (!$data) { + my $child = scalar(@$stack) ? $map_path_to_info->($api->{children}, $stack, $uri_param) : $api; + + if (!($child && $child->{info} && $child->{info}->{$method})) { return undef if $noerr; die "unable to find API method '$method' for path '$path'\n"; } - return $data; + return $child->{info}->{$method}; } sub complete_api_call_options { @@ -174,10 +219,18 @@ sub complete_api_call_options { &$print_result(@option_list); } +sub merge_api_definition_properties { + my ($path, $method, $properties) = @_; + + my $info = PVE::APIClient::Helpers::find_method_info($path, $method); + + return { %{$info->{parameters}->{properties}}, %$properties }; +} + sub complete_api_path { my ($text) = @_; - get_api_definition(); # make sure API data is loaded + my $api = get_api_definition(); # make sure API data is loaded $text =~ s!^/!!; @@ -186,28 +239,40 @@ sub complete_api_path { my $info; if (!defined($dir)) { $dir = ''; - $info = { children => $pve_api_definition }; + $info = $api; } else { - $info = $pve_api_path_hash->{"/$dir"}; + my $stack = [ grep { length($_) > 0 } split('\/+' , $dir)]; # skip empty fragments + $info = $map_path_to_info->($api->{children}, $stack, {}); } my $res = []; if ($info) { + my $prefix = length($dir) ? "/$dir/" : '/'; if (my $children = $info->{children}) { foreach my $c (@$children) { - if ($c->{path} =~ m!\Q$dir/$rest!) { - push @$res, $c->{path}; - push @$res, "$c->{path}/" if $c->{children}; + my $ctext = $c->{text}; + if ($ctext =~ m/^\{(\S+)\}$/) { + push @$res, "$prefix$ctext"; + push @$res, "$prefix$ctext/"; + if (length($rest)) { + push @$res, "$prefix$rest"; + push @$res, "$prefix$rest/"; + } + } elsif ($ctext =~ m/^\Q$rest/) { + push @$res, "$prefix$ctext"; + push @$res, "$prefix$ctext/" if $c->{children}; } } } } + return $res; } # test for command lines with api calls (or similar bash completion calls): # example1: pveclient api get remote1 /cluster sub extract_path_info { + my ($uri_param) = @_; my $info; @@ -219,7 +284,7 @@ sub extract_path_info { my $path = $args->[4]; if (my $method = $method_map->{$args->[2]}) { - $info = lookup_api_method($path, $method, 1); + $info = find_method_info($path, $method, $uri_param, 1); } }; @@ -257,19 +322,42 @@ sub get_vmid_resource { } sub poll_task { - my ($conn, $node, $upid) = @_; + my ($conn, $node, $upid, $quiet) = @_; my $path = "api2/json/nodes/$node/tasks/$upid/status"; my $task_status; + my $last_line = 0; while(1) { + if (!$quiet) { + my $path = "api2/json/nodes/$node/tasks/$upid/log"; + my $task_log = $conn->get($path, {start => $last_line}); + + my $printme = ''; + for my $li (@$task_log) { + if ($li->{t} eq 'no content') { + next; + } + $printme .= $li->{t} . "\n"; + $last_line = $li->{n}; + } + + if ($printme ne '') { + print $printme; + } + } + $task_status = $conn->get($path, {}); if ($task_status->{status} eq "stopped") { last; } - sleep(10); + sleep(2); + } + + if ($task_status->{exitstatus} ne "OK") { + die $task_status->{exitstatus}; } return $task_status->{exitstatus};