]> git.proxmox.com Git - pve-common.git/blobdiff - data/PVE/JSONSchema.pm
register standard options for SPICE
[pve-common.git] / data / PVE / JSONSchema.pm
index e87c22e4d7bced5b301a56326dcb119877778cc9..881120769778d944ef706357244ef0faaff1d8a2 100644 (file)
@@ -1,13 +1,14 @@
 package PVE::JSONSchema;
 
-use warnings;
 use strict;
+use warnings;
 use Storable; # for dclone
 use Getopt::Long;
 use Devel::Cycle -quiet; # todo: remove?
 use PVE::Tools qw(split_list);
 use PVE::Exception qw(raise);
 use HTTP::Status qw(:constants);
+use Net::IP qw(:PROC);
 
 use base 'Exporter';
 
@@ -70,6 +71,11 @@ register_standard_option('pve-iface', {
     minLength => 2, maxLength => 20,
 });
 
+PVE::JSONSchema::register_standard_option('pve-storage-id', {
+    description => "The storage identifier.",
+    type => 'string', format => 'pve-storage-id',
+}); 
+
 my $format_list = {};
 
 sub register_format {
@@ -91,11 +97,23 @@ sub pve_verify_configid {
  
     if ($id !~ m/^[a-z][a-z0-9_]+$/i) {
        return undef if $noerr;
-       die "invalid cofiguration ID '$id'\n"; 
+       die "invalid configuration ID '$id'\n"; 
     }
     return $id;
 }
 
+PVE::JSONSchema::register_format('pve-storage-id', \&parse_storage_id);
+sub parse_storage_id {
+    my ($storeid, $noerr) = @_;
+
+    if ($storeid !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) {
+       return undef if $noerr;
+       die "storage ID '$storeid' contains illegal characters\n";
+    }
+    return $storeid;
+}
+
+
 register_format('pve-vmid', \&pve_verify_vmid);
 sub pve_verify_vmid {
     my ($vmid, $noerr) = @_;
@@ -111,8 +129,7 @@ register_format('pve-node', \&pve_verify_node_name);
 sub pve_verify_node_name {
     my ($node, $noerr) = @_;
 
-    # todo: use better regex ?
-    if ($node !~ m/^[A-Za-z][[:alnum:]\-]*[[:alnum:]]+$/) {
+    if ($node !~ m/^([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)$/) {
        return undef if $noerr;
        die "value does not look like a valid node name\n";
     }
@@ -123,21 +140,51 @@ register_format('ipv4', \&pve_verify_ipv4);
 sub pve_verify_ipv4 {
     my ($ipv4, $noerr) = @_;
 
-   if ($ipv4 !~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ ||
-       !(($1 > 0) && ($1 < 255) &&
-        ($2 <= 255) && ($3 <= 255) && 
-        ($4 > 0) && ($4 < 255)))  {
-          return undef if $noerr;
+    if (!Net::IP::ip_is_ipv4($ipv4))  {
+       return undef if $noerr;
        die "value does not look like a valid IP address\n";
     }
     return $ipv4;
 }
+
+my $ipv4_mask_hash = {
+    '128.0.0.0' => 1,
+    '192.0.0.0' => 2,
+    '224.0.0.0' => 3,
+    '240.0.0.0' => 4,
+    '248.0.0.0' => 5,
+    '252.0.0.0' => 6,
+    '254.0.0.0' => 7,
+    '255.0.0.0' => 8,
+    '255.128.0.0' => 9,
+    '255.192.0.0' => 10,
+    '255.224.0.0' => 11,
+    '255.240.0.0' => 12,
+    '255.248.0.0' => 13,
+    '255.252.0.0' => 14,
+    '255.254.0.0' => 15,
+    '255.255.0.0' => 16,
+    '255.255.128.0' => 17,
+    '255.255.192.0' => 18,
+    '255.255.224.0' => 19,
+    '255.255.240.0' => 20,
+    '255.255.248.0' => 21,
+    '255.255.252.0' => 22,
+    '255.255.254.0' => 23,
+    '255.255.255.0' => 24,
+    '255.255.255.128' => 25,
+    '255.255.255.192' => 26,
+    '255.255.255.224' => 27,
+    '255.255.255.240' => 28,
+    '255.255.255.248' => 29,
+    '255.255.255.252' => 30
+};
+
 register_format('ipv4mask', \&pve_verify_ipv4mask);
 sub pve_verify_ipv4mask {
     my ($mask, $noerr) = @_;
 
-    if ($mask !~ m/^255\.255\.(\d{1,3})\.(\d{1,3})$/ ||
-       !(($1 <= 255) && ($2 <= 255)))  {
+    if (!defined($ipv4_mask_hash->{$mask})) {
        return undef if $noerr;
        die "value does not look like a valid IP netmask\n";
     }
@@ -156,6 +203,19 @@ sub pve_verify_email {
     return $email;
 }
 
+register_format('dns-name', \&pve_verify_dns_name);
+sub pve_verify_dns_name {
+    my ($name, $noerr) = @_;
+
+    my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
+
+    if ($name !~ /^(${namere}\.)*${namere}$/) {
+          return undef if $noerr;
+          die "value does not look like a valid DNS name\n";
+    }
+    return $name;
+}
+
 # network interface name
 register_format('pve-iface', \&pve_verify_iface);
 sub pve_verify_iface {
@@ -168,12 +228,29 @@ sub pve_verify_iface {
     return $id;
 }
 
+register_standard_option('spice-proxy', {
+    description => "SPICE proxy server. This can be used by the client to specify the proxy server. All nodes in a cluster runs 'spiceproxy', so it is up to the client to choose one. By default, we return the node where the VM is currently running. As resonable setting is to use same node you use to connect to the API (This is window.location.hostname for the JS GUI).",
+    type => 'string', format => 'dns-name',
+}); 
+
+register_standard_option('remote-viewer-config', {
+    description => "Returned values can be directly passed to the 'remote-viewer' application.",
+    additionalProperties => 1,
+    properties => {
+       type => { type => 'string' },
+       password => { type => 'string' },
+       proxy => { type => 'string' },
+       host => { type => 'string' },
+       'tls-port' => { type => 'integer' },
+    },
+});
+
 sub check_format {
     my ($format, $value) = @_;
 
     return if $format eq 'regex';
 
-    if ($format =~ m/^(.*)-list$/) {
+    if ($format =~ m/^(.*)-a?list$/) {
        
        my $code = $format_list->{$1};
 
@@ -740,14 +817,21 @@ my $method_schema = {
            optional => 1,
            additionalProperties => 0,
            properties => {
+               description => {
+                    description => "Describe access permissions.",
+                    optional => 1,
+               },
                 user => {
-                    description => "A simply way to allow access for 'all' users. The special value 'arg' allows access for the user specified in the 'username' parameter. This is useful to allow access to things owned by a user, like changing the user password. Value 'world' is used to allow access without credentials.", 
+                    description => "A simply way to allow access for 'all' authenticated users. Value 'world' is used to allow access without credentials.", 
                     type => 'string', 
-                    enum => ['all', 'arg', 'world'],
+                    enum => ['all', 'world'],
                     optional => 1,
                 },
-                path => { type => 'string', optional => 1, requires => 'privs' },
-                privs => { type => 'array', optional => 1, requires => 'path' },
+                check => {
+                    description => "Array of permission checks (prefix notation).",
+                    type => 'array', 
+                    optional => 1 
+                },
             },
         },
         match_name => {
@@ -849,7 +933,7 @@ sub method_get_child_link {
 # a way to parse command line parameters, using a 
 # schema to configure Getopt::Long
 sub get_options {
-    my ($schema, $args, $uri_param, $pwcallback, $list_param) = @_;
+    my ($schema, $args, $arg_param, $fixed_param, $pwcallback) = @_;
 
     if (!$schema || !$schema->{properties}) {
        raise("too many arguments\n", code => HTTP_BAD_REQUEST)
@@ -857,11 +941,19 @@ sub get_options {
        return {};
     }
 
+    my $list_param;
+    if ($arg_param && !ref($arg_param)) {
+       my $pd = $schema->{properties}->{$arg_param};
+       die "expected list format $pd->{format}"
+           if !($pd && $pd->{format} && $pd->{format} =~ m/-list/);
+       $list_param = $arg_param;
+    }
+
     my @getopt = ();
     foreach my $prop (keys %{$schema->{properties}}) {
        my $pd = $schema->{properties}->{$prop};
-       next if $prop eq $list_param;
-       next if defined($uri_param->{$prop});
+       next if $list_param && $prop eq $list_param;
+       next if defined($fixed_param->{$prop});
 
        if ($prop eq 'password' && $pwcallback) {
            # we do not accept plain password on input line, instead
@@ -871,7 +963,7 @@ sub get_options {
        } elsif ($pd->{type} eq 'boolean') {
            push @getopt, "$prop:s";
        } else {
-           if ($pd->{format} && $pd->{format} =~ m/-list/) {
+           if ($pd->{format} && $pd->{format} =~ m/-a?list/) {
                push @getopt, "$prop=s@";
            } else {
                push @getopt, "$prop=s";
@@ -882,18 +974,23 @@ sub get_options {
     my $opts = {};
     raise("unable to parse option\n", code => HTTP_BAD_REQUEST)
        if !Getopt::Long::GetOptionsFromArray($args, $opts, @getopt);
-    
-    if ($list_param) {
-       my $pd = $schema->{properties}->{$list_param} ||
-           die "no schema for list_param";
 
-       $opts->{$list_param} = $args;
-       $args = [];
+    if (my $acount = scalar(@$args)) {
+       if ($list_param) {
+           $opts->{$list_param} = $args;
+           $args = [];
+       } elsif (ref($arg_param)) {
+           raise("wrong number of arguments\n", code => HTTP_BAD_REQUEST)
+               if scalar(@$arg_param) != $acount; 
+           foreach my $p (@$arg_param) {
+               $opts->{$p} = shift @$args;
+           }
+       } else {
+           raise("too many arguments\n", code => HTTP_BAD_REQUEST)
+               if scalar(@$args) != 0;
+       }
     }
 
-    raise("too many arguments\n", code => HTTP_BAD_REQUEST)
-       if scalar(@$args) != 0;
-
     if (my $pd = $schema->{properties}->{password}) {
        if ($pd->{type} ne 'boolean' && $pwcallback) {
            if ($opts->{password} || !$pd->{optional}) {
@@ -901,7 +998,9 @@ sub get_options {
            }
        }
     }
-    
+
+    $opts = PVE::Tools::decode_utf8_parameters($opts);
+
     foreach my $p (keys %$opts) {
        if (my $pd = $schema->{properties}->{$p}) {
            if ($pd->{type} eq 'boolean') {
@@ -914,12 +1013,13 @@ sub get_options {
                } else {
                    raise("unable to parse boolean option\n", code => HTTP_BAD_REQUEST);
                }
-           } elsif ($pd->{format} && $pd->{format} =~ m/-list/) {
+           } elsif ($pd->{format}) {
 
-               if ($pd->{format} eq 'pve-vmid-list') {
+               if ($pd->{format} =~ m/-list/) {
                    # allow --vmid 100 --vmid 101 and --vmid 100,101
+                   # allow --dow mon --dow fri and --dow mon,fri
                    $opts->{$p} = join(",", @{$opts->{$p}});
-               } else {
+               } elsif ($pd->{format} =~ m/-alist/) {
                    # we encode array as \0 separated strings
                    # Note: CGI.pm also use this encoding
                    if (scalar(@{$opts->{$p}}) != 1) {
@@ -934,8 +1034,8 @@ sub get_options {
        }       
     }
 
-    foreach my $p (keys %$uri_param) {
-       $opts->{$p} = $uri_param->{$p};
+    foreach my $p (keys %$fixed_param) {
+       $opts->{$p} = $fixed_param->{$p};
     }
 
     return $opts;