diff --git a/src/PVE/SectionConfig.pm b/src/PVE/SectionConfig.pm index 99ee348..a18e9d8 100644 --- a/src/PVE/SectionConfig.pm +++ b/src/PVE/SectionConfig.pm @@ -10,102 +10,65 @@ use PVE::Exception qw(raise_param_exc); use PVE::JSONSchema qw(get_standard_option); use PVE::Tools; -=pod - -=head1 NAME - -SectionConfig - -=head1 DESCRIPTION - -This package provides a way to have multiple (often similar) types of entries -in the same config file, each in its own section, thus I
. - -Under the hood, this package automatically creates and manages a matching -I for one's plugin architecture that is used to represent data -that is read from and written to the config file. - -Where this config file is located, as well as its permissions and other related -things, is up to the plugin author and is not handled by C -at all. - -=head1 USAGE - -The intended structure is to have a single I that inherits from -this class and provides meaningful defaults in its C<$defaultData>, such as a -default list of core C I. The I is -thus very similar to an I. - -Each I is then defined in its own package that should inherit -from the I and defines which I it itself provides and -uses, as well as which I it uses from the I. - -The methods that need to be implemented are annotated in the L section -below. - - ┌─────────────────┐ - │ SectionConfig │ - └────────┬────────┘ - │ - │ - │ - ┌────────▼────────┐ - │ BasePlugin │ - └────────┬────────┘ - │ - ┌─────────┴─────────┐ - │ │ - ┌────────▼────────┐ ┌────────▼────────┐ - │ConcretePluginFoo│ │ConcretePluginBar│ - └─────────────────┘ └─────────────────┘ - -=head2 REGISTERING PLUGINS - -In order to actually be able to use plugins, they must first be I -and then I via the "base" plugin: - - use PVE::Example::BasePlugin; - use PVE::Example::PluginA; - use PVE::Example::PluginB; - - PVE::Example::PluginA->register(); - PVE::Example::PluginB->register(); - PVE::Example::BasePlugin->init(); - -=head2 MODES - -There are two modes for how I are exposed. - -=head3 unified mode (default) - -In this mode there is only a global list of I which the child -plugins can use. This has the consequence that it's not possible to define the -same property name more than once in different plugins. - -The reason behind this behaviour is to ensure that properties with the same -name don't behave in different ways, or in other words, to enforce the use of -identical properties for multiple plugins. - -=head3 isolated mode - -This mode can be used by calling C with an additional parameter: - - PVE::Example::BasePlugin->init(property_isolation => 1); - -With this mode each I gets its own isolated list of I, -or in other words, a fully isolated schema namespace. Normally one wants to use -C schemas when enabling isolation. - -Note that in this mode it's only necessary to specify a I in the -C method when it's either C or stems from the global list of -I. - -All locally defined I of a I are automatically added -to its schema. - -=head2 METHODS - -=cut +# This package provides a way to have multiple (often similar) types of entries +# in the same config file, each in its own section, thus "Section Config". +# +# The intended structure is to have a single 'base' plugin that inherits from +# this class and provides meaningful defaults in its '$defaultData', e.g. a +# default list of the core properties in its propertyList (most often only 'id' +# and 'type') +# +# Each 'real' plugin then has it's own package that should inherit from the +# 'base' plugin and returns it's specific properties in the 'properties' method, +# its type in the 'type' method and all the known options, from both parent and +# itself, in the 'options' method. +# The options method can also be used to define if a property is 'optional' or +# 'fixed' (only settable on config entity-creation), for example: +# +# ```` +# sub options { +# return { +# 'some-optional-property' => { optional => 1 }, +# 'a-fixed-property' => { fixed => 1 }, +# 'a-required-but-not-fixed-property' => {}, +# }; +# } +# ``` +# +# 'fixed' options can be set on create, but not changed afterwards. +# +# To actually use it, you have to first register all the plugins and then init +# the 'base' plugin, like so: +# +# ``` +# use PVE::Dummy::Plugin1; +# use PVE::Dummy::Plugin2; +# use PVE::Dummy::BasePlugin; +# +# PVE::Dummy::Plugin1->register(); +# PVE::Dummy::Plugin2->register(); +# PVE::Dummy::BasePlugin->init(); +# ``` +# +# There are two modes for how properties are exposed, the default 'unified' +# mode and the 'isolated' mode. +# In the default unified mode, there is only a global list of properties +# which the plugins can use, so you cannot define the same property name twice +# in different plugins. The reason for this is to force the use of identical +# properties for multiple plugins. +# +# The second way is to use the 'isolated' mode, which can be achieved by +# calling init with `1` as its parameter like this: +# +# ``` +# PVE::Dummy::BasePlugin->init(property_isolation => 1); +# ``` +# +# With this, each plugin get's their own isolated list of properties which it +# can use. Note that in this mode, you only have to specify the property in the +# options method when it is either 'fixed' or comes from the global list of +# properties. All locally defined ones get automatically added to the schema +# for that plugin. my $defaultData = { options => {}, @@ -114,85 +77,11 @@ my $defaultData = { propertyList => {}, }; -=pod - -=head3 private - -B Must be implemented in the I. - - $data = PVE::Example::Plugin->private() - $data = $class->private() - -Getter for C-related private data. - -Most commonly this is used to simply retrieve the default I list of -one's plugin architecture, for example: - - use PVE::JSONSchema qw(get_standard_option); - - use base qw(PVE::SectionConfig); - - # [...] - - my $defaultData = { - propertyList => { - type => { - description => "Type of plugin." - }, - nodes => get_standard_option('pve-node-list', { - description => "List of nodes for which the plugin applies.", - optional => 1, - }), - disable => { - description => "Flag to disable the plugin.", - type => 'boolean', - optional => 1, - }, - 'max-foo-rate' => { - description => "Maximum 'foo' rate of the plugin. Use '-1' for unlimited.", - type => 'integer', - minimum => -1, - default => 42, - optional => 1, - }, - # [...] - }, - }; - - sub private { - return $defaultData; - } - -=cut - sub private { die "overwrite me"; return $defaultData; } -=pod - -=head3 register - - PVE::Example::Plugin->register() - -Used to register I. - -This method must be called on each child plugin before I the base -plugin. - -For example: - - use PVE::Example::BasePlugin; - use PVE::Example::PluginA; - use PVE::Example::PluginB; - - PVE::Example::PluginA->register(); - PVE::Example::PluginB->register(); - PVE::Example::BasePlugin->init(); - -=cut - sub register { my ($class) = @_; @@ -207,127 +96,22 @@ sub register { $pdata->{plugins}->{$type} = $class; } -=pod - -=head3 type - -B Must be implemented in I. - - $type = PVE::Example::Plugin->type() - $type = $class->type() - -Returns the I of a I, which is a I string. This is -used to identify the I. - -Should be overridden on I: - - sub type { - return "foo"; - } - -=cut - sub type { die "overwrite me"; } -=pod - -=head3 properties - -B Must be implemented in I. - - $props = PVE::Example::Plugin->properties() - $props = $class->properties() - -Returns the I specific to a I as a hash. - - sub properties() { - return { - path => { - description => "Path used to retrieve a 'foo'.", - type => 'string', - format => 'some-custom-format-handler-for-paths', - }, - is_bar = { - description => "Whether the 'foo' is 'bar' or not.", - type => 'boolean', - }, - bwlimit => get_standard_option('bwlimit'), - }; - } - -=cut - sub properties { return {}; } -=pod - -=head3 options - -B Must be implemented in I. - - $opts = PVE::Example::Plugin->options() - $opts = $class->options() - -This method is used to specify which I are actually configured for -a given I. More precisely, only the I that are -contained in the hash this method returns can be used. - -Additionally, it also allows to declare whether a property is C or -C. - - sub options { - return { - 'some-optional-property' => { optional => 1 }, - 'a-fixed-property' => { fixed => 1 }, - 'a-required-but-not-fixed-property' => {}, - }; - } - -C I are not required to be set. - -C I may only be set on creation of the config entity. - -=cut - sub options { return {}; } -=pod - -=head3 plugindata - -B Can be implemented in I. - - $plugindata = PVE::Example::Plugin->plugindata() - $plugindata = $class->plugindata() - -This method is used by plugin authors to provide any kind of data specific to -their plugin implementation and is otherwise not touched by C. - -This mostly exists for convenience and doesn't need to be implemented. - -=cut - sub plugindata { return {}; } -=pod - -=head3 has_isolated_properties - - $is_isolated = PVE::Example::Plugin->has_isolated_properties() - $is_isolated = $class->has_isolated_properties() - -Checks whether the plugin has isolated I (runs in isolated mode). - -=cut - sub has_isolated_properties { my ($class) = @_; @@ -384,34 +168,6 @@ my sub add_property { } }; -=pod - -=head3 createSchema - - $schema = PVE::Example::Plugin->($skip_type, $base) - $schema = $class->($skip_type, $base) - -Returns the C used for I instances of a -I. - -This schema may then be used as desired, for example as the definition of -parameters of an API handler (C). - -=over - -=item C<$skip_type> (optional) - -Can be set to C<1> if there's a I named "type" in the list of -default I that should be excluded from the generated schema. - -=item C<$base> (optional) - -The I to use per default. - -=back - -=cut - sub createSchema { my ($class, $skip_type, $base) = @_; @@ -486,18 +242,6 @@ sub createSchema { }; } -=pod - -=head3 updateSchema - -Returns the C used for I instances of a -I. - -This schema may then be used as desired, for example as the definition of -parameters of an API handler (C). - -=cut - sub updateSchema { my ($class, $single_class, $base) = @_; @@ -582,22 +326,12 @@ sub updateSchema { }; } -=pod - -=head3 init - - $base_plugin->init(); - $base_plugin->init(property_isolation => 1); - -This method is used to initialize all I that have been -I beforehand. - -Optionally, it is also possible to pass C as parameter in -order to activate I. See L in the package-level -documentation for more information. - -=cut - +# the %param hash controls some behavior of the section config, currently the following options are +# understood: +# +# - property_isolation: if set, each child-plugin has a fully isolated property (schema) namespace. +# By default this is off, meaning all child-plugins share the schema of properties with the same +# name. Normally one wants to use oneOf schema's when enabling isolation. sub init { my ($class, %param) = @_; @@ -658,18 +392,6 @@ sub init { $propertyList->{type}->{enum} = [sort keys %$plugins]; } -=pod - -=head3 lookup - - $plugin = PVE::Example::BasePlugin->lookup($type) - $plugin = $class->lookup($type) - -Returns the I corresponding to the given C or dies if it -cannot be found. - -=cut - sub lookup { my ($class, $type) = @_; @@ -683,17 +405,6 @@ sub lookup { return $plugin; } -=pod - -=head3 lookup_types - - $types = PVE::Example::BasePlugin->lookup_types() - $types = $class->lookup_types() - -Returns a list of all I Cs. - -=cut - sub lookup_types { my ($class) = @_; @@ -702,66 +413,18 @@ sub lookup_types { return [ sort keys %{$pdata->{plugins}} ]; } -=pod - -=head3 decode_value - -B Can be implemented in the I. - - $decoded_value = PVE::Example::BasePlugin->decode_value($type, $key, $value) - $decoded_value = $class->($type, $key, $value) - -Called during C in order to convert values that have been read -from a C file which have been I beforehand by -C. - -Does nothing to C<$value> by default, but can be overridden in the I -in order to implement custom conversion behavior. - -=cut - sub decode_value { my ($class, $type, $key, $value) = @_; return $value; } -=pod - -=head3 encode_value - -B Can be implemented in the I. - - $encoded_value = PVE::Example::BasePlugin->encode_value($type, $key, $value) - $encoded_value = $class->($type, $key, $value) - -Called during C in order to convert values into a serializable -format. - -Does nothing to C<$value> by default, but can be overridden in the I -in order to implement custom conversion behavior. Usually one should also -override C in a matching manner. - -=cut - sub encode_value { my ($class, $type, $key, $value) = @_; return $value; } -=pod - -=head3 check_value - - $checked_value = PVE::Example::BasePlugin->check_value($type, $key, $value, $storeid, $skipSchemaCheck) - $checked_value = $class->check_value($type, $key, $value, $storeid, $skipSchemaCheck) - -Used internally to check if various invariants are upheld. It's best to not -override this. - -=cut - sub check_value { my ($class, $type, $key, $value, $storeid, $skipSchemaCheck) = @_; @@ -810,46 +473,6 @@ sub check_value { return $value; } -=pod - -=head3 parse_section_header - -B Can be I in the I. - - ($type, $sectionId, $errmsg, $config) = PVE::Example::BasePlugin->parse_section_header($line) - ($type, $sectionId, $errmsg, $config) = $class->parse_section_header($line) - -Parses the header of a section and returns an array containing the section's -C, ID and optionally an error message as well as additional config -attributes. - -Can be overriden on the I in order to provide custom logic for -handling the header, e.g. if the section IDs need to be parsed or validated in -a certain way. - -Note that the section B initially be parsed with the regex used by the -original method when overriding in order to guarantee compatibility. -For example: - - sub parse_section_header { - my ($class, $line) = @_; - - if ($line =~ m/^(\S):\s*(\S+)\s*$/) { - my ($type, $sectionId) = ($1, $2); - - my $errmsg = undef; - eval { check_section_id_is_valid($sectionId); }; - $errmsg = $@ if $@; - - my $config = parse_extra_stuff_from_section_id($sectionId); - - return ($type, $sectionId, $errmsg, $config); - } - return undef; - } - -=cut - sub parse_section_header { my ($class, $line) = @_; @@ -862,40 +485,12 @@ sub parse_section_header { return undef; } -=pod - -=head3 format_section_header - -B Can be overridden in the I. - - $header = PVE::Example::BasePlugin->format_section_header($type, $sectionId, $scfg, $done_hash) - $header = $class->format_section_header($type, $sectionId, $scfg, $done_hash) - -Formats the header of a section. Simply C<"$type: $sectionId\n"> by default. - -Note that when overriding this, the header B end with a newline (C<\n>). -One also might want to add a matching override for C. - -=cut - sub format_section_header { my ($class, $type, $sectionId, $scfg, $done_hash) = @_; return "$type: $sectionId\n"; } -=pod - -=head3 get_property_schema - - $schema = PVE::Example::BasePlugin->get_property_schema($type, $key) - $schema = $class->get_property_schema($type, $key) - -Returns the schema of a I of a I that is denoted via -its C<$type>. - -=cut - sub get_property_schema { my ($class, $type, $key) = @_; @@ -911,106 +506,6 @@ sub get_property_schema { return $schema; } -=pod - -=head3 parse_config - - $config = PVE::Example::BasePlugin->parse_config($filename, $raw, $allow_unknown) - $config = $class->parse_config($filename, $raw, $allow_unknown) - -Parses the contents of a C file and returns a complex nested -hash which not only contains the parsed data, but additional information that -one may or may not find useful. More below. - -=over - -=item C<$filename> - -The name of the file whose content is stored in C<$raw>. - -=item C<$raw> - -The raw content of C<$filename>. - -=item C<$allow_unknown> - -Whether to allow parsing unknown I. - -=back - -The returned hash is structured as follows: - - { - ids => { - foo => { - key => value, - ... - }, - bar => { - key => value, - ... - }, - }, - order => { - foo => 1, - bar => 2, - }, - digest => "5f5513f8822fdbe5145af33b64d8d970dcf95c6e", - errors => ( - { - context => ..., - section => "section ID", - key => "some_key", - err => "error message", - }, - ... - ), - } - -=over - -=item C - -Each section's parsed configuration values, or more precisely, the I
and their associated configuration options as returned by -C. - -=item C - -The order in which the sections in C were parsed. - -=item C - -A SHA1 hex digest of the contents in C<$raw>. - -=item C (optional) - -An optional list of error hashes, where each hash contains the following keys: - -=over 2 - -=item C - -In which file and in which line the error was encountered. - -=item C
- -In which section the error was encountered. - -=item C - -Which I the error corresponds to. - -=item C - -The error. - -=back - -=back - -=cut - sub parse_config { my ($class, $filename, $raw, $allow_unknown) = @_; @@ -1147,23 +642,6 @@ sub parse_config { return $cfg; } -=pod - -=head3 check_config - - $settings = PVE::Example::BasePlugin->check_config($sectionId, $config, $create, $skipSchemaCheck) - $settings = $class->check_config($sectionId, $config, $create, $skipSchemaCheck) - -Does not just check whether a section's configuration is valid, despite its -name, but also calls C (among other things) internally. - -Returns a hash which contains all I for the given C<$sectionId>. -In other words, all configured key-value pairs for the provided section. - -It's best to not override this. - -=cut - sub check_config { my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_; @@ -1222,52 +700,6 @@ my $format_config_line = sub { } }; -=pod - -=head3 write_config - - $output = PVE::Example::BasePlugin->write_config($filename, $cfg, $allow_unknown) - $output = $class->write_config($filename, $cfg, $allow_unknown) - -Generates the output that should be written to the C file. - -=over - -=item C<$filename> (unused) - -The name of the file to which the generated output will be written to. -This parameter is currently unused and has no effect. - -=item C<$cfg> - -The hash that represents the entire configuration that should be written. -This hash is expected to have the following format: - - { - ids => { - foo => { - key => value, - ... - }, - bar => { - key => value, - ... - }, - }, - order => { - foo => 1, - bar => 2, - }, - } - -=item C<$allow_unknown> - -Whether to allow writing sections with an unknown C. - -=back - -=cut - sub write_config { my ($class, $filename, $cfg, $allow_unknown) = @_; @@ -1366,45 +798,6 @@ sub assert_if_modified { PVE::Tools::assert_if_modified($cfg->{digest}, $digest); } -=pod - -=head3 delete_from_config - - $config = PVE::Example::BasePlugin->delete_from_config($config, $option_schema, $new_options, $to_delete) - $config = $class->delete_from_config($config, $option_schema, $new_options, $to_delete) - -Convenience method to delete key from a hash of configured I which -performs necessary checks beforehand. - -Note: The passed C<$config> is modified in place and also returned. - -=over - -=item C<$config> - -The section's configuration that the given I in C<$to_delete> should -be deleted from. - -=item C<$option_schema> - -The schema of the I associated with C<$config>. See the C -method. - -=item C<$new_options> - -The I which are to be added to C<$config>. Note that this method -doesn't add any I itself; this is to prohibit simultaneously -setting and deleting the same I. - -=item C<$to_delete> - -A reference to an array containing the names of the I to delete -from C<$config>. - -=back - -=cut - sub delete_from_config { my ($config, $option_schema, $new_options, $to_delete) = @_;