On Wed, Apr 11, 2018 at 10:08:48AM +0200, Fabian Grünbichler wrote: > this currently only contains a description and the node-specific ACME > configuration, but I am sure we can find other goodies to put there. > > Signed-off-by: Fabian Grünbichler <f.gruenbich...@proxmox.com> > --- > PVE/API2/Makefile | 1 + > PVE/Makefile | 1 + > PVE/API2/NodeConfig.pm | 99 ++++++++++++++++++++++++ > PVE/API2/Nodes.pm | 7 ++ > PVE/NodeConfig.pm | 205 > +++++++++++++++++++++++++++++++++++++++++++++++++ > 5 files changed, 313 insertions(+) > create mode 100644 PVE/API2/NodeConfig.pm > create mode 100644 PVE/NodeConfig.pm > > diff --git a/PVE/API2/Makefile b/PVE/API2/Makefile > index 86d75d36..51b8b30a 100644 > --- a/PVE/API2/Makefile > +++ b/PVE/API2/Makefile > @@ -14,6 +14,7 @@ PERLSOURCE = \ > Pool.pm \ > Tasks.pm \ > Network.pm \ > + NodeConfig.pm \ > Services.pm > > all: > diff --git a/PVE/Makefile b/PVE/Makefile > index 395faf8a..56d27d13 100644 > --- a/PVE/Makefile > +++ b/PVE/Makefile > @@ -11,6 +11,7 @@ PERLSOURCE = \ > AutoBalloon.pm \ > CephTools.pm \ > Report.pm \ > + NodeConfig.pm \ > VZDump.pm > > all: pvecfg.pm ${SUBDIRS} > diff --git a/PVE/API2/NodeConfig.pm b/PVE/API2/NodeConfig.pm > new file mode 100644 > index 00000000..8c976974 > --- /dev/null > +++ b/PVE/API2/NodeConfig.pm > @@ -0,0 +1,99 @@ > +package PVE::API2::NodeConfig; > + > +use strict; > +use warnings; > + > +use PVE::JSONSchema qw(get_standard_option); > +use PVE::NodeConfig; > +use PVE::Tools qw(extract_param); > + > +use base qw(PVE::RESTHandler); > + > +my $node_config_schema = PVE::NodeConfig::get_nodeconfig_schema(); > +my $node_config_properties = { > + delete => { > + type => 'string', format => 'pve-configid-list', > + description => "A list of settings you want to delete.", > + optional => 1, > + }, > + digest => { > + type => 'string', > + description => 'Prevent changes if current configuration file has > different SHA1 digest. This can be used to prevent concurrent modifications.', > + maxLength => 40, > + optional => 1, > + }, > + node => get_standard_option('pve-node'), > +}; > + > +foreach my $opt (keys %{$node_config_schema}) { > + $node_config_properties->{$opt} = $node_config_schema->{$opt}; > +} > + > +__PACKAGE__->register_method({ > + name => 'get_config', > + path => '', > + method => 'GET', > + description => "Get node configuration options.", > + permissions => { > + check => ['perm', '/', [ 'Sys.Audit' ]], > + }, > + parameters => { > + additionalProperties => 0, > + properties => { > + node => get_standard_option('pve-node'), > + }, > + }, > + returns => { > + type => "object", > + properties => {}, > + }, > + code => sub { > + my ($param) = @_; > + > + return PVE::NodeConfig::load_config($param->{node}); > + }}); > + > +__PACKAGE__->register_method({ > + name => 'set_options', > + path => '', > + method => 'PUT', > + description => "Set node configuration options.", > + permissions => { > + check => ['perm', '/', [ 'Sys.Modify' ]], > + }, > + protected => 1, > + parameters => { > + additionalProperties => 0, > + properties => $node_config_properties, > + }, > + returns => { type => "null" }, > + code => sub { > + my ($param) = @_; > + > + my $delete = extract_param($param, 'delete'); > + my $node = extract_param($param, 'node'); > + my $digest = extract_param($param, 'digest'); > + > + my $code = sub { > + my $conf = PVE::NodeConfig::load_config($node); > + > + PVE::Tools::assert_if_modified($digest, $conf->{digest}); > + > + foreach my $opt (keys %$param) { > + $conf->{$opt} = $param->{$opt}; > + } > + > + foreach my $opt (PVE::Tools::split_list($delete)) { > + delete $conf->{$opt}; > + }; > + > + PVE::NodeConfig::write_config($node, $conf); > + }; > + > + PVE::NodeConfig::lock_config($node, $code); > + die $@ if $@; > + > + return undef; > + }}); > + > +1; > diff --git a/PVE/API2/Nodes.pm b/PVE/API2/Nodes.pm > index 3eb38315..42b932cf 100644 > --- a/PVE/API2/Nodes.pm > +++ b/PVE/API2/Nodes.pm > @@ -41,6 +41,7 @@ use PVE::API2::APT; > use PVE::API2::Ceph; > use PVE::API2::Firewall::Host; > use PVE::API2::Replication; > +use PVE::API2::NodeConfig; > use Digest::MD5; > use Digest::SHA; > use PVE::API2::Disks; > @@ -118,6 +119,11 @@ __PACKAGE__->register_method ({ > path => 'replication', > }); > > +__PACKAGE__->register_method ({ > + subclass => "PVE::API2::NodeConfig", > + path => 'config', > +}); > + > __PACKAGE__->register_method ({ > name => 'index', > path => '', > @@ -171,6 +177,7 @@ __PACKAGE__->register_method ({ > { name => 'stopall' }, > { name => 'netstat' }, > { name => 'firewall' }, > + { name => 'config' }, > ]; > > return $result; > diff --git a/PVE/NodeConfig.pm b/PVE/NodeConfig.pm > new file mode 100644 > index 00000000..33317e02 > --- /dev/null > +++ b/PVE/NodeConfig.pm > @@ -0,0 +1,205 @@ > +package PVE::NodeConfig; > + > +use strict; > +use warnings; > + > +use PVE::CertHelpers; > +use PVE::JSONSchema qw(get_standard_option); > +use PVE::Tools qw(file_get_contents file_set_contents lock_file); > + > +my $node_config_lock = '/var/lock/pvenode.lock'; > + > +PVE::JSONSchema::register_format('pve-acme-domain', sub { > + my ($domain, $noerr) = @_; > + > + my $label = qr/[a-z][a-z0-9_-]*/i; > + > + return $domain if $domain =~ /^$label(?:\.$label)+$/; > + return undef if $noerr; > + die "value does not look like a valid domain name"; > +}); > + > +sub config_file { > + my ($node) = @_; > + > + return "/etc/pve/nodes/${node}/config"; > +} > + > +sub load_config { > + my ($node) = @_; > + > + my $filename = config_file($node); > + my $raw = eval { PVE::Tools::file_get_contents($filename); }; > + return {} if !$raw; > + > + return parse_node_config($raw); > +} > + > +sub write_config { > + my ($node, $conf) = @_; > + > + my $filename = config_file($node); > + > + my $raw = write_node_config($conf); > + > + PVE::Tools::file_set_contents($filename, $raw); > +} > + > +sub lock_config { > + my ($node, $code, @param) = @_; > + > + my $res = lock_file($node_config_lock, 10, $code, @param); > + > + die $@ if $@; > + > + return $res; > +} > + > +my $confdesc = { > + description => { > + type => 'string', > + description => 'Node description/comment.', > + optional => 1, > + }, > +}; > + > +my $acmedesc = { > + account => get_standard_option('pve-acme-account-name'), > + domains => { > + type => 'string', > + format => 'pve-acme-domain-list', > + format_description => 'domain[;domain;...]', > + description => 'List of domains for this node\'s ACME certificate', > + }, > +}; > +PVE::JSONSchema::register_format('pve-acme-node-conf', $acmedesc); > + > +$confdesc->{acme} = { > + type => 'string', > + description => 'Node specific ACME settings.', > + format => $acmedesc, > + optional => 1, > +}; > + > +sub check_type {
We have this in pve-container and I think qemu-server as well(?), so we should move this to PVE::JSONSchema - which already contains a different function of this name, but that one is private and can simply be renamed as it shouldn't be in use anywhere else atm. > + my ($key, $value) = @_; > + > + die "unknown setting '$key'\n" if !$confdesc->{$key}; > + > + my $type = $confdesc->{$key}->{type}; > + > + if (!defined($value)) { > + die "got undefined value\n"; > + } > + > + if ($value =~ m/[\n\r]/) { > + die "property contains a line feed\n"; > + } > + > + if ($type eq 'boolean') { > + return 1 if ($value eq '1') || ($value =~ m/^(on|yes|true)$/i); > + return 0 if ($value eq '0') || ($value =~ m/^(off|no|false)$/i); > + die "type check ('boolean') failed - got '$value'\n"; > + } elsif ($type eq 'integer') { > + return int($1) if $value =~ m/^(\d+)$/; > + die "type check ('integer') failed - got '$value'\n"; > + } elsif ($type eq 'number') { > + return $value if $value =~ m/^(\d+)(\.\d+)?$/; > + die "type check ('number') failed - got '$value'\n"; > + } elsif ($type eq 'string') { > + if (my $fmt = $confdesc->{$key}->{format}) { > + PVE::JSONSchema::check_format($fmt, $value); > + return $value; > + } > + return $value; > + } else { > + die "internal error" > + } > +} > +sub parse_node_config { > + my ($content) = @_; > + > + return undef if !defined($content); > + > + my $conf = { > + digest => Digest::SHA::sha1_hex($content), > + }; > + my $descr = ''; > + > + my @lines = split(/\n/, $content); > + foreach my $line (@lines) { > + if ($line =~ /^\#(.*)\s*$/) { > + $descr .= PVE::Tools::decode_text($1) . "\n"; > + next; > + } > + if ($line =~ /^description:\s*(.*\S)\s*$/) { > + $descr .= PVE::Tools::decode_text($1) . "\n"; > + next; > + } > + if ($line =~ /^([a-z][a-z_]*\d*):\s*(\S.*)\s*$/) { > + my $key = $1; > + my $value = $2; > + eval { $value = check_type($key, $value); }; > + warn "cannot parse value of '$key' in node config: $@" if $@; > + $conf->{$key} = $value; > + } else { > + warn "cannot parse line '$line' in node config\n"; > + } > + } > + > + $conf->{description} = $descr if $descr; > + > + return $conf; > +} > + > +sub write_node_config { > + my ($conf) = @_; > + > + my $raw = ''; > + # add description as comment to top of file > + my $descr = $conf->{description} || ''; > + foreach my $cl (split(/\n/, $descr)) { > + $raw .= '#' . PVE::Tools::encode_text($cl) . "\n"; > + } > + > + for my $key (sort keys %$conf) { > + next if ($key eq 'description'); > + next if ($key eq 'digest'); > + > + my $value = $conf->{$key}; > + die "detected invalid newline inside property '$key'\n" > + if $value =~ m/\n/; > + $raw .= "$key: $value\n"; > + } > + > + return $raw; > +} > + > +sub parse_acme { > + my ($data, $noerr) = @_; > + > + $data //= ''; > + > + my $res = eval { PVE::JSONSchema::parse_property_string($acmedesc, > $data); }; > + if ($@) { > + return undef if $noerr; > + die $@; > + } > + > + $res->{domains} = [ PVE::Tools::split_list($res->{domains}) ]; > + > + return $res; > +} > + > +sub print_acme { > + my ($acme) = @_; > + > + $acme->{domains} = join(';', $acme->{domains}) if $acme->{domains}; > + return PVE::JSONSchema::print_property_string($acme, $acmedesc); > +} > + > +sub get_nodeconfig_schema { > + return $confdesc; > +} > + > +1; > -- > 2.14.2 > > > _______________________________________________ > pve-devel mailing list > pve-devel@pve.proxmox.com > https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel