]>
git.proxmox.com Git - pve-common.git/blob - src/PVE/SectionConfig.pm
1 package PVE
::SectionConfig
;
9 use PVE
::Exception
qw(raise_param_exc);
10 use PVE
::JSONSchema
qw(get_standard_option);
27 my $type = $class->type();
28 my $pdata = $class->private();
30 die "duplicate plugin registration (type = $type)"
31 if defined($pdata->{plugins
}->{$type});
33 my $plugindata = $class->plugindata();
34 $pdata->{plugindata
}->{$type} = $plugindata;
35 $pdata->{plugins
}->{$type} = $class;
55 my ($class, $skip_type, $base) = @_;
57 my $pdata = $class->private();
58 my $propertyList = $pdata->{propertyList
};
59 my $plugins = $pdata->{plugins
};
61 my $props = $base || {};
63 my $copy_property = sub {
67 foreach my $k (keys %$src) {
68 $res->{$k} = $src->{$k};
74 foreach my $p (keys %$propertyList) {
75 next if $skip_type && $p eq 'type';
77 if (!$propertyList->{$p}->{optional
}) {
78 $props->{$p} = $propertyList->{$p};
84 my $copts = $class->options();
85 $required = 0 if defined($copts->{$p}) && $copts->{$p}->{optional
};
87 foreach my $t (keys %$plugins) {
88 my $opts = $pdata->{options
}->{$t} || {};
89 $required = 0 if !defined($opts->{$p}) || $opts->{$p}->{optional
};
93 # make a copy, because we modify the optional property
94 my $res = &$copy_property($propertyList->{$p});
98 $props->{$p} = $propertyList->{$p};
104 additionalProperties
=> 0,
105 properties
=> $props,
110 my ($class, $single_class, $base) = @_;
112 my $pdata = $class->private();
113 my $propertyList = $pdata->{propertyList
};
114 my $plugins = $pdata->{plugins
};
116 my $props = $base || {};
118 my $filter_type = $single_class ?
$class->type() : undef;
120 foreach my $p (keys %$propertyList) {
121 next if $p eq 'type';
123 my $copts = $class->options();
125 next if defined($filter_type) && !defined($copts->{$p});
127 if (!$propertyList->{$p}->{optional
}) {
128 $props->{$p} = $propertyList->{$p};
134 $modifyable = 1 if defined($copts->{$p}) && !$copts->{$p}->{fixed
};
136 foreach my $t (keys %$plugins) {
137 my $opts = $pdata->{options
}->{$t} || {};
138 next if !defined($opts->{$p});
139 $modifyable = 1 if !$opts->{$p}->{fixed
};
141 next if !$modifyable;
143 $props->{$p} = $propertyList->{$p};
146 $props->{digest
} = get_standard_option
('pve-config-digest');
149 type
=> 'string', format
=> 'pve-configid-list',
150 description
=> "A list of settings you want to delete.",
157 additionalProperties
=> 0,
158 properties
=> $props,
165 my $pdata = $class->private();
167 foreach my $k (qw(options plugins plugindata propertyList)) {
168 $pdata->{$k} = {} if !$pdata->{$k};
171 my $plugins = $pdata->{plugins
};
172 my $propertyList = $pdata->{propertyList
};
174 foreach my $type (keys %$plugins) {
175 my $props = $plugins->{$type}->properties();
176 foreach my $p (keys %$props) {
177 die "duplicate property '$p'" if defined($propertyList->{$p});
178 my $res = $propertyList->{$p} = {};
179 my $data = $props->{$p};
180 for my $a (keys %$data) {
181 $res->{$a} = $data->{$a};
183 $res->{optional
} = 1;
187 foreach my $type (keys %$plugins) {
188 my $opts = $plugins->{$type}->options();
189 foreach my $p (keys %$opts) {
190 die "undefined property '$p'" if !$propertyList->{$p};
192 $pdata->{options
}->{$type} = $opts;
195 $propertyList->{type
}->{type
} = 'string';
196 $propertyList->{type
}->{enum
} = [sort keys %$plugins];
200 my ($class, $type) = @_;
202 croak
"cannot lookup undefined type!" if !defined($type);
204 my $pdata = $class->private();
205 my $plugin = $pdata->{plugins
}->{$type};
207 die "unknown section type '$type'\n" if !$plugin;
215 my $pdata = $class->private();
217 return [ sort keys %{$pdata->{plugins
}} ];
221 my ($class, $type, $key, $value) = @_;
227 my ($class, $type, $key, $value) = @_;
233 my ($class, $type, $key, $value, $storeid, $skipSchemaCheck) = @_;
235 my $pdata = $class->private();
237 return $value if $key eq 'type' && $type eq $value;
239 my $opts = $pdata->{options
}->{$type};
240 die "unknown section type '$type'\n" if !$opts;
242 die "unexpected property '$key'\n" if !defined($opts->{$key});
244 my $schema = $pdata->{propertyList
}->{$key};
245 die "unknown property type\n" if !$schema;
247 my $ct = $schema->{type
};
249 $value = 1 if $ct eq 'boolean' && !defined($value);
251 die "got undefined value\n" if !defined($value);
253 die "property contains a line feed\n" if $value =~ m/[\n\r]/;
255 if (!$skipSchemaCheck) {
258 my $checkschema = $schema;
260 if ($ct eq 'array') {
261 die "no item schema for array" if !defined($schema->{items
});
262 $checkschema = $schema->{items
};
265 PVE
::JSONSchema
::check_prop
($value, $checkschema, '', $errors);
266 if (scalar(keys %$errors)) {
267 die "$errors->{$key}\n" if $errors->{$key};
268 die "$errors->{_root}\n" if $errors->{_root
};
269 die "unknown error\n";
273 if ($ct eq 'boolean' || $ct eq 'integer' || $ct eq 'number') {
274 return $value + 0; # convert to number
280 sub parse_section_header
{
281 my ($class, $line) = @_;
283 if ($line =~ m/^(\S+):\s*(\S+)\s*$/) {
284 my ($type, $sectionId) = ($1, $2);
285 my $errmsg = undef; # set if you want to skip whole section
286 my $config = {}; # to return additional attributes
287 return ($type, $sectionId, $errmsg, $config);
292 sub format_section_header
{
293 my ($class, $type, $sectionId, $scfg, $done_hash) = @_;
295 return "$type: $sectionId\n";
300 my ($class, $filename, $raw, $allow_unknown) = @_;
302 my $pdata = $class->private();
307 $raw = '' if !defined($raw);
309 my $digest = Digest
::SHA
::sha1_hex
($raw);
314 my @lines = split(/\n/, $raw);
316 while (defined(my $line = shift @lines)) {
318 return $line if ($line !~ /^\s*#/);
323 my ($type, $key) = @_;
325 my $schema = $pdata->{propertyList
}->{$key};
326 die "unknown property type\n" if !$schema;
328 return $schema->{type
} eq 'array';
333 my $line = $nextline->();
336 my $errprefix = "file $filename line $lineno";
338 my ($type, $sectionId, $errmsg, $config) = $class->parse_section_header($line);
348 warn "$errprefix (skip section '$sectionId'): $errmsg\n";
351 warn "$errprefix (skip section '$sectionId'): missing type - internal error\n";
353 if (!($plugin = $pdata->{plugins
}->{$type})) {
354 if ($allow_unknown) {
358 warn "$errprefix (skip section '$sectionId'): unsupported type '$type'\n";
363 while ($line = $nextline->()) {
364 next if $skip; # skip
366 $errprefix = "file $filename line $lineno";
368 if ($line =~ m/^\s+(\S+)(\s+(.*\S))?\s*$/) {
369 my ($k, $v) = ($1, $3);
372 if ($is_array->($type, $k)) {
374 $v = $plugin->check_value($type, $k, $v, $sectionId);
376 $config->{$k} = [] if !defined($config->{$k});
377 push $config->{$k}->@*, $v;
379 die "duplicate attribute\n" if defined($config->{$k});
381 $v = $plugin->check_value($type, $k, $v, $sectionId);
387 warn "$errprefix (section '$sectionId') - unable to parse value of '$k': $err";
389 context
=> $errprefix,
390 section
=> $sectionId,
397 warn "$errprefix (section '$sectionId') - ignore config line: $line\n";
402 $config->{type
} = $type;
403 $ids->{$sectionId} = $config;
404 $order->{$sectionId} = $pri++;
405 } elsif (!$skip && $type && $plugin && $config) {
406 $config->{type
} = $type;
408 $config = eval { $config = $plugin->check_config($sectionId, $config, 1, 1); };
409 warn "$errprefix (skip section '$sectionId'): $@" if $@;
411 $ids->{$sectionId} = $config;
412 $order->{$sectionId} = $pri++;
416 warn "$errprefix - ignore config line: $line\n";
425 $cfg->{errors
} = $errors if scalar(@$errors) > 0;
431 my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_;
433 my $type = $class->type();
434 my $pdata = $class->private();
435 my $opts = $pdata->{options
}->{$type};
437 my $settings = { type
=> $type };
439 foreach my $k (keys %$config) {
440 my $value = $config->{$k};
442 die "can't change value of fixed parameter '$k'\n"
443 if !$create && defined($opts->{$k}) && $opts->{$k}->{fixed
};
445 if (defined($value)) {
446 my $tmp = $class->check_value($type, $k, $value, $sectionId, $skipSchemaCheck);
447 $settings->{$k} = $class->decode_value($type, $k, $tmp);
449 die "got undefined value for option '$k'\n";
454 # check if we have a value for all required options
455 foreach my $k (keys %$opts) {
456 next if $opts->{$k}->{optional
};
457 die "missing value for required option '$k'\n"
458 if !defined($config->{$k});
465 my $format_config_line = sub {
466 my ($schema, $key, $value) = @_;
468 my $ct = $schema->{type
};
470 die "property '$key' contains a line feed\n"
471 if ($key =~ m/[\n\r]/) || ($value =~ m/[\n\r]/);
473 if ($ct eq 'boolean') {
474 return "\t$key " . ($value ?
1 : 0) . "\n"
476 } elsif ($ct eq 'array') {
477 die "property '$key' is not an array" if ref($value) ne 'ARRAY';
479 for my $line ($value->@*) {
480 $result .= "\t$key $line\n" if $value ne '';
484 return "\t$key $value\n" if "$value" ne '';
489 my ($class, $filename, $cfg, $allow_unknown) = @_;
491 my $pdata = $class->private();
492 my $propertyList = $pdata->{propertyList
};
496 my $ids = $cfg->{ids
};
497 my $order = $cfg->{order
};
500 foreach my $sectionId (keys %$ids) {
501 my $pri = $order->{$sectionId};
502 $maxpri = $pri if $pri && $pri > $maxpri;
504 foreach my $sectionId (keys %$ids) {
505 if (!defined ($order->{$sectionId})) {
506 $order->{$sectionId} = ++$maxpri;
510 foreach my $sectionId (sort {$order->{$a} <=> $order->{$b}} keys %$ids) {
511 my $scfg = $ids->{$sectionId};
512 my $type = $scfg->{type
};
513 my $opts = $pdata->{options
}->{$type};
515 die "unknown section type '$type'\n" if !$opts && !$allow_unknown;
519 my $data = $class->format_section_header($type, $sectionId, $scfg, $done_hash);
521 if (!$opts && $allow_unknown) {
522 $done_hash->{type
} = 1;
523 my @first = exists($scfg->{comment
}) ?
('comment') : ();
524 for my $k (@first, sort keys %$scfg) {
525 next if defined($done_hash->{$k});
526 $done_hash->{$k} = 1;
528 $data .= "\t$k $v\n";
535 if ($scfg->{comment
} && !$done_hash->{comment
}) {
537 my $v = $class->encode_value($type, $k, $scfg->{$k});
538 $data .= &$format_config_line($propertyList->{$k}, $k, $v);
541 $data .= "\tdisable\n" if $scfg->{disable
} && !$done_hash->{disable
};
543 $done_hash->{comment
} = 1;
544 $done_hash->{disable
} = 1;
546 my @option_keys = sort keys %$opts;
547 foreach my $k (@option_keys) {
548 next if defined($done_hash->{$k});
549 next if $opts->{$k}->{optional
};
550 $done_hash->{$k} = 1;
552 die "section '$sectionId' - missing value for required option '$k'\n"
554 $v = $class->encode_value($type, $k, $v);
555 $data .= &$format_config_line($propertyList->{$k}, $k, $v);
558 foreach my $k (@option_keys) {
559 next if defined($done_hash->{$k});
561 next if !defined($v);
562 $v = $class->encode_value($type, $k, $v);
563 $data .= &$format_config_line($propertyList->{$k}, $k, $v);
572 sub assert_if_modified
{
573 my ($cfg, $digest) = @_;
575 PVE
::Tools
::assert_if_modified
($cfg->{digest
}, $digest);
578 sub delete_from_config
{
579 my ($config, $option_schema, $new_options, $to_delete) = @_;
581 for my $k ($to_delete->@*) {
582 my $d = $option_schema->{$k} || die "no such option '$k'\n";
583 die "unable to delete required option '$k'\n" if !$d->{optional
};
584 die "unable to delete fixed option '$k'\n" if $d->{fixed
};
585 die "cannot set and delete property '$k' at the same time!\n"
586 if defined($new_options->{$k});
587 delete $config->{$k};