X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=data%2FPVE%2FRESTHandler.pm;h=91b269ea2ab3d0d6ee6ac0b0ea219aaf027ae3b9;hb=39313ced7edf3dc9069328390bba38f638920fd9;hp=47f66afb6bbac5fa0ffee805cf93756a45ac7549;hpb=e143e9d86b489c57166b5c192bf41dcc3452471e;p=pve-common.git diff --git a/data/PVE/RESTHandler.pm b/data/PVE/RESTHandler.pm index 47f66af..91b269e 100644 --- a/data/PVE/RESTHandler.pm +++ b/data/PVE/RESTHandler.pm @@ -2,7 +2,6 @@ package PVE::RESTHandler; use strict; no strict 'refs'; # our autoload requires this - use warnings; use PVE::SafeSyslog; use PVE::Exception qw(raise raise_param_exc); @@ -14,6 +13,7 @@ use Storable qw(dclone); my $method_registry = {}; my $method_by_name = {}; +my $method_path_lookup = {}; our $AUTOLOAD; # it's a package global @@ -157,25 +157,76 @@ sub register_method { my $match_re = []; my $match_name = []; + my $errprefix; + + my $method; + if ($info->{subclass}) { + $errprefix = "register subclass $info->{subclass} at ${self}/$info->{path} -"; + $method = 'SUBCLASS'; + } else { + $errprefix = "register method ${self}/$info->{path} -"; + $info->{method} = 'GET' if !$info->{method}; + $method = $info->{method}; + } + + $method_path_lookup->{$self} = {} if !defined($method_path_lookup->{$self}); + my $path_lookup = $method_path_lookup->{$self}; + + die "$errprefix no path" if !defined($info->{path}); + foreach my $comp (split(/\/+/, $info->{path})) { - die "path compoment has zero length" if $comp eq ''; + die "$errprefix path compoment has zero length\n" if $comp eq ''; + my ($name, $regex); if ($comp =~ m/^\{(\w+)(:(.*))?\}$/) { - my $name = $1; - push @$match_re, $3 ? $3 : '\S+'; - push @$match_name, $1; + $name = $1; + $regex = $3 ? $3 : '\S+'; + push @$match_re, $regex; + push @$match_name, $name; + } else { + $name = $comp; + push @$match_re, $name; + push @$match_name, undef; + } + + if ($regex) { + $path_lookup->{regex} = {} if !defined($path_lookup->{regex}); + + my $old_name = $path_lookup->{regex}->{match_name}; + die "$errprefix found changed regex match name\n" + if defined($old_name) && ($old_name ne $name); + my $old_re = $path_lookup->{regex}->{match_re}; + die "$errprefix found changed regex\n" + if defined($old_re) && ($old_re ne $regex); + $path_lookup->{regex}->{match_name} = $name; + $path_lookup->{regex}->{match_re} = $regex; + + die "$errprefix path match error - regex and fixed items\n" + if defined($path_lookup->{folders}); + + $path_lookup = $path_lookup->{regex}; + } else { - push @$match_re, $comp; - push @$match_name, undef; + $path_lookup->{folders}->{$name} = {} if !defined($path_lookup->{folders}->{$name}); + + die "$errprefix path match error - regex and fixed items\n" + if defined($path_lookup->{regex}); + + $path_lookup = $path_lookup->{folders}->{$name}; } } + die "$errprefix duplicate method definition\n" + if defined($path_lookup->{$method}); + + $path_lookup->{$method} = $info; + $info->{match_re} = $match_re; $info->{match_name} = $match_name; $method_by_name->{$self} = {} if !defined($method_by_name->{$self}); if ($info->{name}) { - die "method '${self}::$info->{name}' already defined\n" + die "$errprefix method name already defined\n" if defined($method_by_name->{$self}->{$info->{name}}); $method_by_name->{$self}->{$info->{name}} = $info; @@ -184,6 +235,31 @@ sub register_method { push @{$method_registry->{$self}}, $info; } +sub register_page_formatter { + my ($self, %config) = @_; + + my $format = $config{format} || + die "missing format"; + + my $path = $config{path} || + die "missing path"; + + my $method = $config{method} || + die "missing method"; + + my $code = $config{code} || + die "missing formatter code"; + + my $uri_param = {}; + my ($handler, $info) = $self->find_handler($method, $path, $uri_param); + die "unabe to find handler for '$method: $path'" if !($handler && $info); + + die "duplicate formatter for '$method: $path'" + if $info->{formatter} && $info->{formatter}->{$format}; + + $info->{formatter}->{$format} = $code; +} + sub AUTOLOAD { my ($this) = @_; @@ -218,97 +294,77 @@ sub map_method_by_name { return $info; } -sub map_method { - my ($self, $stack, $method, $uri_param) = @_; - - my $ma = $method_registry->{$self}; +sub map_path_to_methods { + my ($class, $stack, $uri_param, $pathmatchref) = @_; - my $stacklen = scalar(@$stack); + my $path_lookup = $method_path_lookup->{$class}; - #syslog ('info', "MAPTEST:$method:$self: " . join ('/', @$stack)); + # Note: $pathmatchref can be used to obtain path including + # uri patterns like '/cluster/firewall/groups/{group}'. + # Used by pvesh to display help + if (defined($pathmatchref)) { + $$pathmatchref = '' if !$$pathmatchref; + } - foreach my $info (@$ma) { - #syslog ('info', "TEST0 " . Dumper($info)); - next if !($info->{subclass} || ($info->{method} eq $method)); - my $regexlen = scalar(@{$info->{match_re}}); - if ($info->{subclass}) { - next if $stacklen < $regexlen; + while (defined(my $comp = shift @$stack)) { + return undef if !$path_lookup; # not registerd? + if ($path_lookup->{regex}) { + my $name = $path_lookup->{regex}->{match_name}; + my $regex = $path_lookup->{regex}->{match_re}; + + return undef if $comp !~ m/^($regex)$/; + $uri_param->{$name} = $1; + $path_lookup = $path_lookup->{regex}; + $$pathmatchref .= '/{' . $name . '}' if defined($pathmatchref); + } elsif ($path_lookup->{folders}) { + $path_lookup = $path_lookup->{folders}->{$comp}; + $$pathmatchref .= '/' . $comp if defined($pathmatchref); } else { - next if $stacklen != $regexlen; - } - - #syslog ('info', "TEST1 " . Dumper($info)); - - my $param = {}; - my $i = 0; - for (; $i < $regexlen; $i++) { - my $comp = $stack->[$i]; - my $re = $info->{match_re}->[$i]; - #print "COMPARE $comp $info->{match_re}->[$i]\n"; - my ($match) = $stack->[$i] =~ m/^($re)$/; - last if !defined($match); - if (my $name = $info->{match_name}->[$i]) { - $param->{$name} = $match; - } - } - - next if $i != $regexlen; - - #print "MATCH $info->{name}\n"; - - foreach my $p (keys %$param) { - $uri_param->{$p} = $param->{$p}; + die "internal error"; } + + return undef if !$path_lookup; - return $info; - } -} - -sub __find_handler_full { - my ($class, $method, $stack, $uri_param, $pathmatchref) = @_; - - my $info; - eval { - $info = $class->map_method($stack, $method, $uri_param); - }; - syslog('err', $@) if $@; - - return undef if !$info; - - $$pathmatchref .= '/' . $info->{path}; + if (my $info = $path_lookup->{SUBCLASS}) { + $class = $info->{subclass}; - if (my $subh = $info->{subclass}) { + my $fd = $info->{fragmentDelimiter}; - my $matchlen = scalar(@{$info->{match_re}}); + if (defined($fd)) { + # we only support the empty string '' (match whole URI) + die "unsupported fragmentDelimiter '$fd'" + if $fd ne ''; - for (my $i = 0; $i < $matchlen; $i++) { - shift @$stack; # pop from stack + $stack = [ join ('/', @$stack) ] if scalar(@$stack) > 1; + } + $path_lookup = $method_path_lookup->{$class}; } + } - my $fd = $info->{fragmentDelimiter}; + return undef if !$path_lookup; - if (defined($fd)) { + return ($class, $path_lookup); +} - # we only support the empty string '' (match whole URI) - die "unsupported fragmentDelimiter '$fd'" - if $fd ne ''; +sub find_handler { + my ($class, $method, $path, $uri_param, $pathmatchref) = @_; - $stack = [ join ('/', @$stack) ] if scalar(@$stack) > 1; - } + my $stack = [ grep { length($_) > 0 } split('\/+' , $path)]; # skip empty fragments - return $subh->__find_handler_full($method, $stack, $uri_param, $pathmatchref); - } + my ($handler_class, $path_info); + eval { + ($handler_class, $path_info) = $class->map_path_to_methods($stack, $uri_param, $pathmatchref); + }; + my $err = $@; + syslog('err', $err) if $err; - return ($class, $info, $$pathmatchref); -}; + return undef if !($handler_class && $path_info); -sub find_handler { - my ($class, $method, $path, $uri_param) = @_; + my $method_info = $path_info->{$method}; - my $stack = [ grep { length($_) > 0 } split('\/+' , $path)]; # skip empty fragments + return undef if !$method_info; - my $pathmatch = ''; - return $class->__find_handler_full($method, $stack, $uri_param, \$pathmatch); + return ($handler_class, $method_info); } sub handle { @@ -344,7 +400,8 @@ sub handle { # # $name ... the name of the method # $prefix ... usually something like "$exename $cmd" ('pvesm add') -# $arg_param ... list of parameters we want to get as ordered arguments on the command line +# $arg_param ... list of parameters we want to get as ordered arguments +# on the command line (or single parameter name for lists) # $fixed_param ... do not generate and info about those parameters # $format: # 'long' ... default (list all options) @@ -365,12 +422,20 @@ sub usage_str { my $arg_hash = {}; my $args = ''; + + $arg_param = [ $arg_param ] if $arg_param && !ref($arg_param); + foreach my $p (@$arg_param) { next if !$prop->{$p}; # just to be sure + my $pd = $prop->{$p}; $arg_hash->{$p} = 1; $args .= " " if $args; - $args .= $prop->{$p} && $prop->{$p}->{optional} ? "[<$p>]" : "<$p>"; + if ($pd->{format} && $pd->{format} =~ m/-list/) { + $args .= "{<$p>}"; + } else { + $args .= $pd->{optional} ? "[<$p>]" : "<$p>"; + } } my $get_prop_descr = sub { @@ -468,19 +533,9 @@ sub cli_handler { my $info = $self->map_method_by_name($name); - my $param; - foreach my $p (keys %$fixed_param) { - $param->{$p} = $fixed_param->{$p}; - } - - foreach my $p (@$arg_param) { - $param->{$p} = shift @$args if $args->[0] && $args->[0] !~ m/^-/; - } - my $res; eval { - my $param = PVE::JSONSchema::get_options($info->{parameters}, $args, $param, $pwcallback); - + my $param = PVE::JSONSchema::get_options($info->{parameters}, $args, $arg_param, $fixed_param, $pwcallback); $res = $self->handle($info, $param); }; if (my $err = $@) {