From eaf20c2f298317719ded08cfda27016adcaa3a4f Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Tue, 5 Jun 2018 07:51:00 +0200 Subject: [PATCH] imported SectionConfig from pve-common --- Makefile | 1 + PVE/SectionConfig.pm | 497 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 498 insertions(+) create mode 100644 PVE/SectionConfig.pm diff --git a/Makefile b/Makefile index c9bbbbe..71b56e1 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,7 @@ install: pve-api-definition.js install -m 0644 PVE/RESTHandler.pm ${LIB_DIR}/PVE install -m 0644 PVE/CLIHandler.pm ${LIB_DIR}/PVE install -m 0644 PVE/PTY.pm ${LIB_DIR}/PVE + install -m 0644 PVE/SectionConfig.pm ${LIB_DIR}/PVE # install pveclient install -D -m 0644 PVE/APIClient/Helpers.pm ${LIB_DIR}/PVE/APIClient/Helpers.pm install -D -m 0644 PVE/APIClient/Config.pm ${LIB_DIR}/PVE/APIClient/Config.pm diff --git a/PVE/SectionConfig.pm b/PVE/SectionConfig.pm new file mode 100644 index 0000000..cc03aea --- /dev/null +++ b/PVE/SectionConfig.pm @@ -0,0 +1,497 @@ +package PVE::SectionConfig; + +use strict; +use warnings; +use Digest::SHA; +use PVE::Exception qw(raise_param_exc); +use PVE::JSONSchema qw(get_standard_option); + +use Data::Dumper; + +my $defaultData = { + options => {}, + plugins => {}, + plugindata => {}, + propertyList => {}, +}; + +sub private { + die "overwrite me"; + return $defaultData; +} + +sub register { + my ($class) = @_; + + my $type = $class->type(); + my $pdata = $class->private(); + + die "duplicate plugin registration (type = $type)" + if defined($pdata->{plugins}->{$type}); + + my $plugindata = $class->plugindata(); + $pdata->{plugindata}->{$type} = $plugindata; + $pdata->{plugins}->{$type} = $class; +} + +sub type { + die "overwrite me"; +} + +sub properties { + return {}; +} + +sub options { + return {}; +} + +sub plugindata { + return {}; +} + +sub createSchema { + my ($class, $skip_type) = @_; + + my $pdata = $class->private(); + my $propertyList = $pdata->{propertyList}; + my $plugins = $pdata->{plugins}; + + my $props = {}; + + my $copy_property = sub { + my ($src) = @_; + + my $res = {}; + foreach my $k (keys %$src) { + $res->{$k} = $src->{$k}; + } + + return $res; + }; + + foreach my $p (keys %$propertyList) { + next if $skip_type && $p eq 'type'; + + if (!$propertyList->{$p}->{optional}) { + $props->{$p} = $propertyList->{$p}; + next; + } + + my $required = 1; + + my $copts = $class->options(); + $required = 0 if defined($copts->{$p}) && $copts->{$p}->{optional}; + + foreach my $t (keys %$plugins) { + my $opts = $pdata->{options}->{$t} || {}; + $required = 0 if !defined($opts->{$p}) || $opts->{$p}->{optional}; + } + + if ($required) { + # make a copy, because we modify the optional property + my $res = &$copy_property($propertyList->{$p}); + $res->{optional} = 0; + $props->{$p} = $res; + } else { + $props->{$p} = $propertyList->{$p}; + } + } + + return { + type => "object", + additionalProperties => 0, + properties => $props, + }; +} + +sub updateSchema { + my ($class, $single_class) = @_; + + my $pdata = $class->private(); + my $propertyList = $pdata->{propertyList}; + my $plugins = $pdata->{plugins}; + + my $props = {}; + + my $filter_type = $class->type() if $single_class; + + foreach my $p (keys %$propertyList) { + next if $p eq 'type'; + + my $copts = $class->options(); + + next if defined($filter_type) && !defined($copts->{$p}); + + if (!$propertyList->{$p}->{optional}) { + $props->{$p} = $propertyList->{$p}; + next; + } + + my $modifyable = 0; + + $modifyable = 1 if defined($copts->{$p}) && !$copts->{$p}->{fixed}; + + foreach my $t (keys %$plugins) { + my $opts = $pdata->{options}->{$t} || {}; + next if !defined($opts->{$p}); + $modifyable = 1 if !$opts->{$p}->{fixed}; + } + next if !$modifyable; + + $props->{$p} = $propertyList->{$p}; + } + + $props->{digest} = get_standard_option('pve-config-digest'); + + $props->{delete} = { + type => 'string', format => 'pve-configid-list', + description => "A list of settings you want to delete.", + maxLength => 4096, + optional => 1, + }; + + return { + type => "object", + additionalProperties => 0, + properties => $props, + }; +} + +sub init { + my ($class) = @_; + + my $pdata = $class->private(); + + foreach my $k (qw(options plugins plugindata propertyList)) { + $pdata->{$k} = {} if !$pdata->{$k}; + } + + my $plugins = $pdata->{plugins}; + my $propertyList = $pdata->{propertyList}; + + foreach my $type (keys %$plugins) { + my $props = $plugins->{$type}->properties(); + foreach my $p (keys %$props) { + die "duplicate property '$p'" if defined($propertyList->{$p}); + my $res = $propertyList->{$p} = {}; + my $data = $props->{$p}; + for my $a (keys %$data) { + $res->{$a} = $data->{$a}; + } + $res->{optional} = 1; + } + } + + foreach my $type (keys %$plugins) { + my $opts = $plugins->{$type}->options(); + foreach my $p (keys %$opts) { + die "undefined property '$p'" if !$propertyList->{$p}; + } + $pdata->{options}->{$type} = $opts; + } + + $propertyList->{type}->{type} = 'string'; + $propertyList->{type}->{enum} = [sort keys %$plugins]; +} + +sub lookup { + my ($class, $type) = @_; + + my $pdata = $class->private(); + my $plugin = $pdata->{plugins}->{$type}; + + die "unknown section type '$type'\n" if !$plugin; + + return $plugin; +} + +sub lookup_types { + my ($class) = @_; + + my $pdata = $class->private(); + + return [ sort keys %{$pdata->{plugins}} ]; +} + +sub decode_value { + my ($class, $type, $key, $value) = @_; + + return $value; +} + +sub encode_value { + my ($class, $type, $key, $value) = @_; + + return $value; +} + +sub check_value { + my ($class, $type, $key, $value, $storeid, $skipSchemaCheck) = @_; + + my $pdata = $class->private(); + + return $value if $key eq 'type' && $type eq $value; + + my $opts = $pdata->{options}->{$type}; + die "unknown section type '$type'\n" if !$opts; + + die "unexpected property '$key'\n" if !defined($opts->{$key}); + + my $schema = $pdata->{propertyList}->{$key}; + die "unknown property type\n" if !$schema; + + my $ct = $schema->{type}; + + $value = 1 if $ct eq 'boolean' && !defined($value); + + die "got undefined value\n" if !defined($value); + + die "property contains a line feed\n" if $value =~ m/[\n\r]/; + + if (!$skipSchemaCheck) { + my $errors = {}; + PVE::JSONSchema::check_prop($value, $schema, '', $errors); + if (scalar(keys %$errors)) { + die "$errors->{$key}\n" if $errors->{$key}; + die "$errors->{_root}\n" if $errors->{_root}; + die "unknown error\n"; + } + } + + if ($ct eq 'boolean' || $ct eq 'integer' || $ct eq 'number') { + return $value + 0; # convert to number + } + + return $value; +} + +sub parse_section_header { + my ($class, $line) = @_; + + if ($line =~ m/^(\S+):\s*(\S+)\s*$/) { + my ($type, $sectionId) = ($1, $2); + my $errmsg = undef; # set if you want to skip whole section + my $config = {}; # to return additional attributes + return ($type, $sectionId, $errmsg, $config); + } + return undef; +} + +sub format_section_header { + my ($class, $type, $sectionId, $scfg, $done_hash) = @_; + + return "$type: $sectionId\n"; +} + + +sub parse_config { + my ($class, $filename, $raw) = @_; + + my $pdata = $class->private(); + + my $ids = {}; + my $order = {}; + + $raw = '' if !defined($raw); + + my $digest = Digest::SHA::sha1_hex($raw); + + my $pri = 1; + + my $lineno = 0; + my @lines = split(/\n/, $raw); + my $nextline = sub { + while (my $line = shift @lines) { + $lineno++; + return $line if $line !~ /^\s*(?:#|$)/; + } + }; + + while (my $line = &$nextline()) { + my $errprefix = "file $filename line $lineno"; + + my ($type, $sectionId, $errmsg, $config) = $class->parse_section_header($line); + if ($config) { + my $ignore = 0; + + my $plugin; + + if ($errmsg) { + $ignore = 1; + chomp $errmsg; + warn "$errprefix (skip section '$sectionId'): $errmsg\n"; + } elsif (!$type) { + $ignore = 1; + warn "$errprefix (skip section '$sectionId'): missing type - internal error\n"; + } else { + if (!($plugin = $pdata->{plugins}->{$type})) { + $ignore = 1; + warn "$errprefix (skip section '$sectionId'): unsupported type '$type'\n"; + } + } + + while ($line = &$nextline()) { + next if $ignore; # skip + + $errprefix = "file $filename line $lineno"; + + if ($line =~ m/^\s+(\S+)(\s+(.*\S))?\s*$/) { + my ($k, $v) = ($1, $3); + + eval { + die "duplicate attribute\n" if defined($config->{$k}); + $config->{$k} = $plugin->check_value($type, $k, $v, $sectionId); + }; + warn "$errprefix (section '$sectionId') - unable to parse value of '$k': $@" if $@; + + } else { + warn "$errprefix (section '$sectionId') - ignore config line: $line\n"; + } + } + + if (!$ignore && $type && $plugin && $config) { + $config->{type} = $type; + eval { $ids->{$sectionId} = $plugin->check_config($sectionId, $config, 1, 1); }; + warn "$errprefix (skip section '$sectionId'): $@" if $@; + $order->{$sectionId} = $pri++; + } + + } else { + warn "$errprefix - ignore config line: $line\n"; + } + } + + + my $cfg = { ids => $ids, order => $order, digest => $digest}; + + return $cfg; +} + +sub check_config { + my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_; + + my $type = $class->type(); + my $pdata = $class->private(); + my $opts = $pdata->{options}->{$type}; + + my $settings = { type => $type }; + + foreach my $k (keys %$config) { + my $value = $config->{$k}; + + die "can't change value of fixed parameter '$k'\n" + if !$create && $opts->{$k}->{fixed}; + + if (defined($value)) { + my $tmp = $class->check_value($type, $k, $value, $sectionId, $skipSchemaCheck); + $settings->{$k} = $class->decode_value($type, $k, $tmp); + } else { + die "got undefined value for option '$k'\n"; + } + } + + if ($create) { + # check if we have a value for all required options + foreach my $k (keys %$opts) { + next if $opts->{$k}->{optional}; + die "missing value for required option '$k'\n" + if !defined($config->{$k}); + } + } + + return $settings; +} + +my $format_config_line = sub { + my ($schema, $key, $value) = @_; + + my $ct = $schema->{type}; + + die "property '$key' contains a line feed\n" + if ($key =~ m/[\n\r]/) || ($value =~ m/[\n\r]/); + + if ($ct eq 'boolean') { + return "\t$key " . ($value ? 1 : 0) . "\n" + if defined($value); + } else { + return "\t$key $value\n" if "$value" ne ''; + } +}; + +sub write_config { + my ($class, $filename, $cfg) = @_; + + my $pdata = $class->private(); + my $propertyList = $pdata->{propertyList}; + + my $out = ''; + + my $ids = $cfg->{ids}; + my $order = $cfg->{order}; + + my $maxpri = 0; + foreach my $sectionId (keys %$ids) { + my $pri = $order->{$sectionId}; + $maxpri = $pri if $pri && $pri > $maxpri; + } + foreach my $sectionId (keys %$ids) { + if (!defined ($order->{$sectionId})) { + $order->{$sectionId} = ++$maxpri; + } + } + + foreach my $sectionId (sort {$order->{$a} <=> $order->{$b}} keys %$ids) { + my $scfg = $ids->{$sectionId}; + my $type = $scfg->{type}; + my $opts = $pdata->{options}->{$type}; + + die "unknown section type '$type'\n" if !$opts; + + my $done_hash = {}; + + my $data = $class->format_section_header($type, $sectionId, $scfg, $done_hash); + if ($scfg->{comment} && !$done_hash->{comment}) { + my $k = 'comment'; + my $v = $class->encode_value($type, $k, $scfg->{$k}); + $data .= &$format_config_line($propertyList->{$k}, $k, $v); + } + + $data .= "\tdisable\n" if $scfg->{disable} && !$done_hash->{disable}; + + $done_hash->{comment} = 1; + $done_hash->{disable} = 1; + + my @option_keys = sort keys %$opts; + foreach my $k (@option_keys) { + next if defined($done_hash->{$k}); + next if $opts->{$k}->{optional}; + $done_hash->{$k} = 1; + my $v = $scfg->{$k}; + die "section '$sectionId' - missing value for required option '$k'\n" + if !defined ($v); + $v = $class->encode_value($type, $k, $v); + $data .= &$format_config_line($propertyList->{$k}, $k, $v); + } + + foreach my $k (@option_keys) { + next if defined($done_hash->{$k}); + my $v = $scfg->{$k}; + next if !defined($v); + $v = $class->encode_value($type, $k, $v); + $data .= &$format_config_line($propertyList->{$k}, $k, $v); + } + + $out .= "$data\n"; + } + + return $out; +} + +sub assert_if_modified { + my ($cfg, $digest) = @_; + + PVE::Tools::assert_if_modified($cfg->{digest}, $digest); +} + +1; -- 2.39.2