]> git.proxmox.com Git - pve-common.git/blobdiff - src/PVE/JSONSchema.pm
schema: take over 'pve-targetstorage' option
[pve-common.git] / src / PVE / JSONSchema.pm
index 20d72b317e801799bbc78f2f19c82ca126a73221..527e40980ffa86661484d0aa236f3e95861d68a9 100644 (file)
@@ -16,9 +16,10 @@ use Data::Dumper;
 use base 'Exporter';
 
 our @EXPORT_OK = qw(
+register_standard_option
 get_standard_option
 parse_property_string
-register_standard_option
+print_property_string
 );
 
 our $CONFIGID_RE = qr/[a-z][a-z0-9_-]+/i;
@@ -82,6 +83,12 @@ register_standard_option('pve-storage-id', {
     type => 'string', format => 'pve-storage-id',
 });
 
+register_standard_option('pve-bridge-id', {
+    description => "Bridge to attach guest network devices to.",
+    type => 'string', format => 'pve-bridge-id',
+    format_description => 'bridge',
+});
+
 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',
@@ -193,6 +200,17 @@ sub parse_storage_id {
     return parse_id($storeid, 'storage', $noerr);
 }
 
+PVE::JSONSchema::register_format('pve-bridge-id', \&parse_bridge_id);
+sub parse_bridge_id {
+    my ($id, $noerr) = @_;
+
+    if ($id !~ m/^[-_.\w\d]+$/) {
+       return undef if $noerr;
+       die "invalid bridge ID '$id'\n";
+    }
+    return $id;
+}
+
 PVE::JSONSchema::register_format('acme-plugin-id', \&parse_acme_plugin_id);
 sub parse_acme_plugin_id {
     my ($pluginid, $noerr) = @_;
@@ -232,6 +250,21 @@ sub pve_verify_node_name {
     return $node;
 }
 
+# maps source to target ID using an ID map
+sub map_id {
+    my ($map, $source) = @_;
+
+    return $source if !defined($map);
+
+    return $map->{entries}->{$source}
+       if $map->{entries} && defined($map->{entries}->{$source});
+
+    return $map->{default} if $map->{default};
+
+    # identity (fallback)
+    return $source;
+}
+
 sub parse_idmap {
     my ($idmap, $idformat) = @_;
 
@@ -273,20 +306,41 @@ sub parse_idmap {
     return $map;
 }
 
-register_format('storagepair', \&verify_storagepair);
-sub verify_storagepair {
-    my ($storagepair, $noerr) = @_;
+my $verify_idpair = sub {
+    my ($input, $noerr, $format) = @_;
 
-    # note: this only checks a single list entry
-    # when using a storagepair-list map, you need to pass the full
-    # parameter to parse_idmap
-    eval { parse_idmap($storagepair, 'pve-storage-id') };
+    eval { parse_idmap($input, $format) };
     if ($@) {
        return undef if $noerr;
        die "$@\n";
     }
 
-    return $storagepair;
+    return $input;
+};
+
+PVE::JSONSchema::register_standard_option('pve-targetstorage', {
+    description => "Mapping from source to target storages. Providing only a single storage ID maps all source storages to that storage. Providing the special value '1' will map each source storage to itself.",
+    type => 'string',
+    format => 'storage-pair-list',
+    optional => 1,
+});
+
+# note: this only checks a single list entry
+# when using a storage-pair-list map, you need to pass the full parameter to
+# parse_idmap
+register_format('storage-pair', \&verify_storagepair);
+sub verify_storagepair {
+    my ($storagepair, $noerr) = @_;
+    return $verify_idpair->($storagepair, $noerr, 'pve-storage-id');
+}
+
+# note: this only checks a single list entry
+# when using a bridge-pair-list map, you need to pass the full parameter to
+# parse_idmap
+register_format('bridge-pair', \&verify_bridgepair);
+sub verify_bridgepair {
+    my ($bridgepair, $noerr) = @_;
+    return $verify_idpair->($bridgepair, $noerr, 'pve-bridge-id');
 }
 
 register_format('mac-addr', \&pve_verify_mac_addr);
@@ -622,12 +676,41 @@ register_standard_option('bwlimit', {
     format => $bwlimit_format,
 });
 
+my $remote_format = {
+    host => {
+       type => 'string',
+       format_description => 'Remote Proxmox hostname or IP',
+    },
+    port => {
+       type => 'integer',
+       optional => 1,
+    },
+    apitoken => {
+       type => 'string',
+       format_description => 'A full Proxmox API token including the secret value.',
+    },
+    fingerprint => get_standard_option(
+       'fingerprint-sha256',
+       {
+           optional => 1,
+           format_description => 'Remote host\'s certificate fingerprint, if not trusted by system store.',
+       }
+    ),
+};
+register_format('proxmox-remote', $remote_format);
+register_standard_option('proxmox-remote', {
+    description => "Specification of a remote endpoint.",
+    type => 'string', format => 'proxmox-remote',
+});
+
+our $PVE_TAG_RE = qr/[a-z0-9_][a-z0-9_\-\+\.]*/i;
+
 # used for pve-tag-list in e.g., guest configs
 register_format('pve-tag', \&pve_verify_tag);
 sub pve_verify_tag {
     my ($value, $noerr) = @_;
 
-    return $value if $value =~ m/^[a-z0-9_][a-z0-9_\-\+\.]*$/i;
+    return $value if $value =~ m/^${PVE_TAG_RE}$/i;
 
     return undef if $noerr;
 
@@ -684,6 +767,18 @@ sub pve_verify_tfa_secret {
     die "unable to decode TFA secret\n";
 }
 
+
+PVE::JSONSchema::register_format('pve-task-status-type', \&verify_task_status_type);
+sub verify_task_status_type {
+    my ($value, $noerr) = @_;
+
+    return $value if $value =~ m/^(ok|error|warning|unknown)$/i;
+
+    return undef if $noerr;
+
+    die "invalid status '$value'\n";
+}
+
 sub check_format {
     my ($format, $value, $path) = @_;
 
@@ -709,13 +804,14 @@ sub check_format {
        if $format_type ne 'none' && ref($registered) ne 'CODE';
 
     if ($format_type eq 'list') {
+       $parsed = [];
        # Note: we allow empty lists
        foreach my $v (split_list($value)) {
-           $parsed = $registered->($v);
+           push @{$parsed}, $registered->($v);
        }
     } elsif ($format_type eq 'opt') {
        $parsed = $registered->($value) if $value;
-   } else {
+    } else {
        if (ref($registered) eq 'HASH') {
            # Note: this is the only case where a validator function could be
            # attached, hence it's safe to handle that in parse_property_string.
@@ -1178,7 +1274,10 @@ sub validate {
     # we can disable that in the final release
     # todo: is there a better/faster way to detect cycles?
     my $cycles = 0;
-    find_cycle($instance, sub { $cycles = 1 });
+    # 'download' responses can contain a filehandle, don't cycle-check that as
+    # it produces a warning
+    my $is_download = ref($instance) eq 'HASH' && exists($instance->{download});
+    find_cycle($instance, sub { $cycles = 1 }) if !$is_download;
     if ($cycles) {
        add_error($errors, undef, "data structure contains recursive cycles");
     } elsif ($schema) {
@@ -1640,11 +1739,15 @@ sub get_options {
                if (!@$args) {
                    # check if all left-over arg_param are optional, else we
                    # must die as the mapping is then ambigious
-                   for (my $j = $i; $j < scalar(@$arg_param); $j++) {
-                       my $prop = $arg_param->[$j];
+                   for (; $i < scalar(@$arg_param); $i++) {
+                       my $prop = $arg_param->[$i];
                        raise("not enough arguments\n", code => HTTP_BAD_REQUEST)
                            if !$schema->{properties}->{$prop}->{optional};
                    }
+                   if ($arg_param->[-1] eq 'extra-args') {
+                       $opts->{'extra-args'} = [];
+                   }
+                   last;
                }
                $opts->{$arg_name} = shift @$args;
            }
@@ -1730,8 +1833,8 @@ sub get_options {
 }
 
 # A way to parse configuration data by giving a json schema
-sub parse_config {
-    my ($schema, $filename, $raw) = @_;
+sub parse_config : prototype($$$;$) {
+    my ($schema, $filename, $raw, $comment_key) = @_;
 
     # do fast check (avoid validate_schema($schema))
     die "got strange schema" if !$schema->{type} ||
@@ -1739,10 +1842,24 @@ sub parse_config {
 
     my $cfg = {};
 
+    my $comment_data;
+    my $handle_comment = sub { $_[0] =~ /^#/ };
+    if (defined($comment_key)) {
+       $comment_data = '';
+       my $comment_re = qr/^\Q$comment_key\E:\s*(.*\S)\s*$/;
+       $handle_comment = sub {
+           if ($_[0] =~ /^\#(.*)\s*$/ || $_[0] =~ $comment_re) {
+               $comment_data .= PVE::Tools::decode_text($1) . "\n";
+               return 1;
+           }
+           return undef;
+       };
+    }
+
     while ($raw =~ /^\s*(.+?)\s*$/gm) {
        my $line = $1;
 
-       next if $line =~ /^#/;
+       next if $handle_comment->($line);
 
        if ($line =~ m/^(\S+?):\s*(.*)$/) {
            my $key = $1;
@@ -1758,6 +1875,10 @@ sub parse_config {
        }
     }
 
+    if (defined($comment_data)) {
+       $cfg->{$comment_key} = $comment_data;
+    }
+
     my $errors = {};
     check_prop($cfg, $schema, '', $errors);