use warnings;
use Storable; # for dclone
use Getopt::Long;
+use Encode::Locale;
+use Encode;
use Devel::Cycle -quiet; # todo: remove?
use PVE::Tools qw(split_list $IPV6RE $IPV4RE);
use PVE::Exception qw(raise);
maxLength => 40, # sha1 hex digest lenght is 40
});
+register_standard_option('skiplock', {
+ description => "Ignore locks - only root is allowed to use this option.",
+ type => 'boolean',
+ optional => 1,
+});
+
register_standard_option('extra-args', {
description => "Extra arguments as array",
type => 'array',
optional => 1
});
+register_standard_option('fingerprint-sha256', {
+ description => "Certificate SHA 256 fingerprint.",
+ type => 'string',
+ pattern => '([A-Fa-f0-9]{2}:){31}[A-Fa-f0-9]{2}',
+});
+
my $format_list = {};
sub register_format {
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;
}
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 MiB/s',
+ },
+ restore => {
+ %bwlimit_opt,
+ description => 'bandwidth limit in MiB/s for restoring guests from backups',
+ },
+ migration => {
+ %bwlimit_opt,
+ description => 'bandwidth limit in MiB/s for migrating guests',
+ },
+ clone => {
+ %bwlimit_opt,
+ description => 'bandwidth limit in MiB/s for cloning disks',
+ },
+ move => {
+ %bwlimit_opt,
+ description => 'bandwidth limit in MiB/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) = @_;
return "${tb}T";
};
+sub parse_boolean {
+ my ($bool) = @_;
+ return 1 if $bool =~ m/^(1|on|yes|true)$/i;
+ return 0 if $bool =~ m/^(0|off|no|false)$/i;
+ return undef;
+}
+
sub parse_property_string {
my ($format, $data, $path, $additional_properties) = @_;
# In property strings we default to not allowing additional properties
$additional_properties = 0 if !defined($additional_properties);
+ # Support named formats here, too:
+ if (!ref($format)) {
+ if (my $desc = $format_list->{$format}) {
+ $format = $desc;
+ } else {
+ die "unknown format: $format\n";
+ }
+ } elsif (ref($format) ne 'HASH') {
+ die "unexpected format value of type ".ref($format)."\n";
+ }
+
my $default_key;
my $res = {};
die "invalid key in comma-separated list property: $k\n" if !$schema;
if ($schema->{type} && $schema->{type} eq 'boolean') {
- $v = 1 if $v =~ m/^(1|on|yes|true)$/i;
- $v = 0 if $v =~ m/^(0|off|no|false)$/i;
+ $v = parse_boolean($v) // $v;
}
$res->{$k} = $v;
} elsif ($part !~ /=/) {
return 1;
#} elsif ($value =~ m/^(0|false|no|off)$/i) {
} elsif ($value eq '0') {
- return 0;
+ return 1; # return success (not value)
} else {
add_error($errors, $path, "type check ('$type') failed - got '$value'");
return undef;
check_prop($value, $requires, $path, $errors);
} elsif (!defined($value->{$requires})) {
add_error($errors, $path ? "$path.$requires" : $requires,
- "missing property - '$newpath' requiers this property");
+ "missing property - '$newpath' requires this property");
}
}
description => "Method needs special privileges - only pvedaemon can execute it",
optional => 1,
},
+ download => {
+ type => 'boolean',
+ description => "Method downloads the file content (filename is the return value of the method).",
+ optional => 1,
+ },
proxyto => {
type => 'string',
description => "A parameter name. If specified, all calls to this method are proxied to the host contained in that parameter.",
optional => 1,
},
+ 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.",
+ optional => 1,
+ },
permissions => {
type => 'object',
description => "Required access permissions. By default only 'root' is allowed to access this method.",
description => "JSON Schema for parameters.",
optional => 1,
},
- formatter => {
- type => 'object',
- description => "Used to store page formatter information (set by PVE::RESTHandler->register_page_formatter).",
- optional => 1,
- },
returns => {
type => 'object',
description => "JSON Schema for return value.",
# 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, $pwcallback, $param_mapping_hash) = @_;
if (!$schema || !$schema->{properties}) {
raise("too many arguments\n", code => HTTP_BAD_REQUEST)
$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) {
+ 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 ($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).
}
}
- $opts = PVE::Tools::decode_utf8_parameters($opts);
+ 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);
+ }
+ }
+
+ # decode after Getopt as we are not sure how well it handles unicode
+ foreach my $p (keys %$opts) {
+ if (!ref($opts->{$p})) {
+ $opts->{$p} = decode('locale', $opts->{$p});
+ } elsif (ref($opts->{$p}) eq 'ARRAY') {
+ my $tmp = [];
+ foreach my $v (@{$opts->{$p}}) {
+ push @$tmp, decode('locale', $v);
+ }
+ $opts->{$p} = $tmp;
+ } elsif (ref($opts->{$p}) eq 'SCALAR') {
+ $opts->{$p} = decode('locale', $$opts->{$p});
+ } else {
+ raise("decoding options failed, unknown reference\n", code => HTTP_BAD_REQUEST);
+ }
+ }
foreach my $p (keys %$opts) {
if (my $pd = $schema->{properties}->{$p}) {
if ($pd->{type} eq 'boolean') {
if ($opts->{$p} eq '') {
$opts->{$p} = 1;
- } elsif ($opts->{$p} =~ m/^(1|true|yes|on)$/i) {
- $opts->{$p} = 1;
- } elsif ($opts->{$p} =~ m/^(0|false|no|off)$/i) {
- $opts->{$p} = 0;
+ } elsif (defined(my $bool = parse_boolean($opts->{$p}))) {
+ $opts->{$p} = $bool;
} else {
raise("unable to parse boolean option\n", code => HTTP_BAD_REQUEST);
}
if ($schema->{properties}->{$key} &&
$schema->{properties}->{$key}->{type} eq 'boolean') {
- $value = 1 if $value =~ m/^(1|on|yes|true)$/i;
- $value = 0 if $value =~ m/^(0|off|no|false)$/i;
+ $value = parse_boolean($value) // $value;
}
$cfg->{$key} = $value;
} else {
};
sub generate_typetext {
- my ($format) = @_;
+ my ($format, $list_enums) = @_;
my ($default_key, $keyAliasProps) = &$find_schema_default_key($format);
} elsif (my $text = $phash->{typetext}) {
$typetext .= $text;
} elsif (my $enum = $phash->{enum}) {
- $typetext .= '<' . join('|', @$enum) . '>';
+ if ($list_enums || (scalar(@$enum) <= 3)) {
+ $typetext .= '<' . join('|', @$enum) . '>';
+ } else {
+ $typetext .= '<enum>';
+ }
} elsif ($phash->{type} eq 'boolean') {
$typetext .= '<1|0>';
} elsif ($phash->{type} eq 'integer') {
}
sub schema_get_type_text {
- my ($phash) = @_;
+ my ($phash, $style) = @_;
my $type = $phash->{type} || 'string';
} elsif ($phash->{format_description}) {
return "<$phash->{format_description}>";
} elsif ($phash->{enum}) {
- return "(" . join(' | ', sort @{$phash->{enum}}) . ")";
+ return "<" . join(' | ', sort @{$phash->{enum}}) . ">";
} elsif ($phash->{pattern}) {
return $phash->{pattern};
} elsif ($type eq 'integer' || $type eq 'number') {
# NOTE: always access values as number (avoid converion to string)
if (defined($phash->{minimum}) && defined($phash->{maximum})) {
- return "$type (" . ($phash->{minimum} + 0) . "-" .
+ return "<$type> (" . ($phash->{minimum} + 0) . " - " .
($phash->{maximum} + 0) . ")";
} elsif (defined($phash->{minimum})) {
- return "$type (" . ($phash->{minimum} + 0) . "- N)";
+ return "<$type> (" . ($phash->{minimum} + 0) . " - N)";
} elsif (defined($phash->{maximum})) {
- return "$type (-N - " . ($phash->{maximum} + 0) . ")";
+ return "<$type> (-N - " . ($phash->{maximum} + 0) . ")";
}
} elsif ($type eq 'string') {
if (my $format = $phash->{format}) {
$format = get_format($format) if ref($format) ne 'HASH';
if (ref($format) eq 'HASH') {
- return generate_typetext($format);
+ my $list_enums = 0;
+ $list_enums = 1 if $style && $style eq 'config-sub';
+ return generate_typetext($format, $list_enums);
}
}
}
- return $type;
+ return "<$type>";
}
1;