]> git.proxmox.com Git - pve-common.git/blobdiff - src/PVE/JSONSchema.pm
JSONSchema: add get_netmask_bits and missing netmask
[pve-common.git] / src / PVE / JSONSchema.pm
index c09a9f474a5413e7c1caa35bd5b9febdebf7eb91..d458ec13f87d57eed2b6d1fab691479dac5cf0a5 100644 (file)
@@ -83,7 +83,7 @@ register_standard_option('pve-config-digest', {
     description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
     type => 'string',
     optional => 1,
-    maxLength => 40, # sha1 hex digest lenght is 40
+    maxLength => 40, # sha1 hex digest length is 40
 });
 
 register_standard_option('skiplock', {
@@ -105,6 +105,20 @@ register_standard_option('fingerprint-sha256', {
     pattern => '([A-Fa-f0-9]{2}:){31}[A-Fa-f0-9]{2}',
 });
 
+register_standard_option('pve-output-format', {
+    type => 'string',
+    description => 'Output format.',
+    enum => [ 'text', 'json', 'json-pretty', 'yaml' ],
+    optional => 1,
+    default => 'text',
+});
+
+register_standard_option('pve-snapshot-name', {
+    description => "The name of the snapshot.",
+    type => 'string', format => 'pve-configid',
+    maxLength => 40,
+});
+
 my $format_list = {};
 
 sub register_format {
@@ -121,6 +135,22 @@ sub get_format {
     return $format_list->{$format};
 }
 
+my $renderer_hash = {};
+
+sub register_renderer {
+    my ($name, $code) = @_;
+
+    die "renderer '$name' already registered\n"
+       if $renderer_hash->{$name};
+
+    $renderer_hash->{$name} = $code;
+}
+
+sub get_renderer {
+    my ($name) = @_;
+    return $renderer_hash->{$name};
+}
+
 # register some common type for pve
 
 register_format('string', sub {}); # allow format => 'string-list'
@@ -180,6 +210,28 @@ sub pve_verify_node_name {
     return $node;
 }
 
+register_format('mac-addr', \&pve_verify_mac_addr);
+sub pve_verify_mac_addr {
+    my ($mac_addr, $noerr) = @_;
+
+    # don't allow I/G bit to be set, most of the time it breaks things, see:
+    # https://pve.proxmox.com/pipermail/pve-devel/2019-March/035998.html
+    if ($mac_addr !~ m/^[a-f0-9][02468ace](?::[a-f0-9]{2}){5}$/i) {
+       return undef if $noerr;
+       die "value does not look like a valid unicast MAC address\n";
+    }
+    return $mac_addr;
+
+}
+register_standard_option('mac-addr', {
+    type => 'string',
+    description => 'Unicast MAC address.',
+    verbose_description => 'A common MAC address with the I/G (Individual/Group) bit not set.',
+    format_description => "XX:XX:XX:XX:XX:XX",
+    optional => 1,
+    format => 'mac-addr',
+});
+
 register_format('ipv4', \&pve_verify_ipv4);
 sub pve_verify_ipv4 {
     my ($ipv4, $noerr) = @_;
@@ -214,6 +266,7 @@ sub pve_verify_ip {
 }
 
 my $ipv4_mask_hash = {
+    '0.0.0.0' => 0,
     '128.0.0.0' => 1,
     '192.0.0.0' => 2,
     '224.0.0.0' => 3,
@@ -248,6 +301,11 @@ my $ipv4_mask_hash = {
     '255.255.255.255' => 32,
 };
 
+sub get_netmask_bits {
+    my ($mask) = @_;
+    return $ipv4_mask_hash->{$mask};
+}
+
 register_format('ipv4mask', \&pve_verify_ipv4mask);
 sub pve_verify_ipv4mask {
     my ($mask, $noerr) = @_;
@@ -263,7 +321,7 @@ register_format('CIDRv6', \&pve_verify_cidrv6);
 sub pve_verify_cidrv6 {
     my ($cidr, $noerr) = @_;
 
-    if ($cidr =~ m!^(?:$IPV6RE)(?:/(\d+))$! && ($1 > 7) &&  ($1 <= 120)) {
+    if ($cidr =~ m!^(?:$IPV6RE)(?:/(\d+))$! && ($1 > 7) && ($1 <= 128)) {
        return $cidr;
     }
 
@@ -321,8 +379,7 @@ register_format('email', \&pve_verify_email);
 sub pve_verify_email {
     my ($email, $noerr) = @_;
 
-    # we use same regex as in Utils.js
-    if ($email !~ /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,63}$/) {
+    if ($email !~ /^[\w\+\-\~]+(\.[\w\+\-\~]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*$/) {
           return undef if $noerr;
           die "value does not look like a valid email address\n";
     }
@@ -379,7 +436,7 @@ sub pve_verify_disk_size {
 }
 
 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).",
+    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 reasonable 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 => 'address',
 }); 
 
@@ -406,6 +463,42 @@ sub pve_verify_startup_order {
     die "unable to parse startup options\n";
 }
 
+my %bwlimit_opt = (
+    optional => 1,
+    type => 'number', minimum => '0',
+    format_description => 'LIMIT',
+);
+
+my $bwlimit_format = {
+       default => {
+           %bwlimit_opt,
+           description => 'default bandwidth limit in KiB/s',
+       },
+       restore => {
+           %bwlimit_opt,
+           description => 'bandwidth limit in KiB/s for restoring guests from backups',
+       },
+       migration => {
+           %bwlimit_opt,
+           description => 'bandwidth limit in KiB/s for migrating guests (including moving local disks)',
+       },
+       clone => {
+           %bwlimit_opt,
+           description => 'bandwidth limit in KiB/s for cloning disks',
+       },
+       move => {
+           %bwlimit_opt,
+           description => 'bandwidth limit in KiB/s for moving disks',
+       },
+};
+register_format('bwlimit', $bwlimit_format);
+register_standard_option('bwlimit', {
+    description => "Set bandwidth/io limits various operations.",
+    optional => 1,
+    type => 'string',
+    format => $bwlimit_format,
+});
+
 sub pve_parse_startup_order {
     my ($value) = @_;
 
@@ -1029,6 +1122,11 @@ my $default_schema_noref = {
            optional => 1,
            description => "This provides the title of the property",
        },
+       renderer => {
+           type => "string",
+           optional => 1,
+           description => "This is used to provide rendering hints to format cli command output.",
+       },
        requires => {
            type => [ "string", "object" ],
            optional => 1,
@@ -1103,6 +1201,11 @@ my $default_schema_noref = {
                },
            },
        },
+       print_width => {
+           type => "integer",
+           description => "For CLI context, this defines the maximal width to print before truncating",
+           optional => 1,
+       },
     }  
 };
 
@@ -1172,7 +1275,7 @@ my $method_schema = {
        },
        proxyto_callback => {
            type =>  'coderef',
-           description => "A function which is called to resolve the proxyto attribute. The default implementaion returns the value of the 'proxyto' parameter.",
+           description => "A function which is called to resolve the proxyto attribute. The default implementation returns the value of the 'proxyto' parameter.",
            optional => 1,
        },
         permissions => {
@@ -1212,7 +1315,7 @@ my $method_schema = {
        },
         fragmentDelimiter => {
             type => 'string',
-           description => "A ways to override the default fragment delimiter '/'. This onyl works on a whole sub-class. You can set this to the empty string to match the whole rest of the URI.",            
+           description => "A way to override the default fragment delimiter '/'. This only works on a whole sub-class. You can set this to the empty string to match the whole rest of the URI.",
            optional => 1,
         },
        parameters => {
@@ -1227,7 +1330,7 @@ my $method_schema = {
        },
         code => {
            type => 'coderef',
-           description => "method implementaion (code reference)",
+           description => "method implementation (code reference)",
            optional => 1,
         },
        subclass => {
@@ -1297,7 +1400,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, $arg_param, $fixed_param, $pwcallback) = @_;
+    my ($schema, $args, $arg_param, $fixed_param, $param_mapping_hash) = @_;
 
     if (!$schema || !$schema->{properties}) {
        raise("too many arguments\n", code => HTTP_BAD_REQUEST)
@@ -1313,17 +1416,19 @@ sub get_options {
        $list_param = $arg_param;
     }
 
+    my @interactive = ();
     my @getopt = ();
     foreach my $prop (keys %{$schema->{properties}}) {
        my $pd = $schema->{properties}->{$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
-           # we turn this into a boolean option and ask for password below
-           # using $pwcallback() (for security reasons).
-           push @getopt, "$prop";
+       my $mapping = $param_mapping_hash->{$prop};
+       if ($mapping && $mapping->{interactive}) {
+           # interactive parameters such as passwords: make the argument
+           # optional and call the mapping function afterwards.
+           push @getopt, "$prop:s";
+           push @interactive, [$prop, $mapping->{func}];
        } elsif ($pd->{type} eq 'boolean') {
            push @getopt, "$prop:s";
        } else {
@@ -1363,13 +1468,24 @@ sub get_options {
            raise("too many arguments\n", code => HTTP_BAD_REQUEST)
                if scalar(@$args) != 0;
        }
+    } else {
+       if (ref($arg_param)) {
+           foreach my $arg_name (@$arg_param) {
+               if ($arg_name eq 'extra-args') {
+                   $opts->{'extra-args'} = [];
+               } else {
+                   raise("not enough arguments\n", code => HTTP_BAD_REQUEST);
+               }
+           }
+       }
     }
 
-    if (my $pd = $schema->{properties}->{password}) {
-       if ($pd->{type} ne 'boolean' && $pwcallback) {
-           if ($opts->{password} || !$pd->{optional}) {
-               $opts->{password} = &$pwcallback(); 
-           }
+    foreach my $entry (@interactive) {
+       my ($opt, $func) = @$entry;
+       my $pd = $schema->{properties}->{$opt};
+       my $value = $opts->{$opt};
+       if (defined($value) || !$pd->{optional}) {
+           $opts->{$opt} = $func->($value);
        }
     }