X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FAPIClient%2FHelpers.pm;h=ec932ec5f261bc2a55d23946cfcfdb7372293fb9;hb=5eb200482a2d7d7127933186e51839ecdff8895c;hp=8a96e8135a7fa47f6d6a94f2b167bd7a11d70fa4;hpb=0ca370283dcfe778a1ff2473e4952dbf13a40683;p=pve-client.git diff --git a/PVE/APIClient/Helpers.pm b/PVE/APIClient/Helpers.pm index 8a96e81..ec932ec 100644 --- a/PVE/APIClient/Helpers.pm +++ b/PVE/APIClient/Helpers.pm @@ -5,13 +5,14 @@ use warnings; use Storable; use JSON; +use File::Path qw(make_path); + 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"; @@ -22,27 +23,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; @@ -100,32 +80,52 @@ 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); } 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 $child = $map_path_to_info->(get_api_definition(), $stack, $uri_param); - if (!$data) { + 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 { @@ -186,26 +186,38 @@ sub complete_api_path { $dir = ''; $info = { children => $pve_api_definition }; } else { - $info = $pve_api_path_hash->{"/$dir"}; + my $stack = [ grep { length($_) > 0 } split('\/+' , $dir)]; # skip empty fragments + $info = $map_path_to_info->($pve_api_definition, $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; @@ -217,7 +229,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); } }; @@ -287,4 +299,57 @@ sub configuration_directory { die "neither XDG_CONFIG_HOME nor HOME environment variable set\n"; } +my $ticket_cache_filename = "/.tickets"; + +sub ticket_cache_lookup { + my ($remote) = @_; + + my $dir = configuration_directory(); + my $filename = "$dir/$ticket_cache_filename"; + + my $data = {}; + eval { $data = from_json(PVE::APIClient::Tools::file_get_contents($filename)); }; + # ignore errors + + my $ticket = $data->{$remote}; + return undef if !defined($ticket); + + my $min_age = - 60; + my $max_age = 3600*2 - 60; + + if ($ticket =~ m/:([a-fA-F0-9]{8})::/) { + my $ttime = hex($1); + my $ctime = time(); + my $age = $ctime - $ttime; + + return $ticket if ($age > $min_age) && ($age < $max_age); + } + + return undef; +} + +sub ticket_cache_update { + my ($remote, $ticket) = @_; + + my $dir = configuration_directory(); + my $filename = "$dir/$ticket_cache_filename"; + + my $code = sub { + make_path($dir); + my $data = {}; + if (-f $filename) { + my $raw = PVE::APIClient::Tools::file_get_contents($filename); + eval { $data = from_json($raw); }; + # ignore errors + } + $data->{$remote} = $ticket; + + PVE::APIClient::Tools::file_set_contents($filename, to_json($data), 0600); + }; + + PVE::APIClient::Tools::lock_file($filename, undef, $code); + die $@ if $@; +} + + 1;