From f725f728410b767758b2ff17f93dd40066135410 Mon Sep 17 00:00:00 2001 From: stdweird Date: Thu, 11 Jun 2020 10:14:30 +0200 Subject: [PATCH] ncm-metaconfig: setup command registry for running arbitrary commands --- .../main/pan/components/metaconfig/schema.pan | 20 ++++- ncm-metaconfig/src/main/perl/metaconfig.pm | 82 +++++++++++++------ ncm-metaconfig/src/test/perl/aii_command.t | 2 +- ncm-metaconfig/src/test/resources/aii.pan | 2 +- .../src/test/resources/commands.pan | 8 +- 5 files changed, 77 insertions(+), 37 deletions(-) diff --git a/ncm-metaconfig/src/main/pan/components/metaconfig/schema.pan b/ncm-metaconfig/src/main/pan/components/metaconfig/schema.pan index 9566a87493..d82f0e0ff0 100644 --- a/ncm-metaconfig/src/main/pan/components/metaconfig/schema.pan +++ b/ncm-metaconfig/src/main/pan/components/metaconfig/schema.pan @@ -86,7 +86,7 @@ type ${project.artifactId}_textrender_convert = { type caf_service_action = string with match(SELF, '^(restart|reload|stop_sleep_start)$'); -type ${project.artifactId}_commands = { +type ${project.artifactId}_actions = { @{Always run, happens before possible modifications} 'pre' ? string @{Always run, happens before possible modifications, the file content is passed om stdin. @@ -130,12 +130,26 @@ type ${project.artifactId}_config = { 'contents' : ${project.artifactId}_extension @{Predefined conversions from EDG::WP4::CCM::TextRender} 'convert' ? ${project.artifactId}_textrender_convert - @{Commands to run on pre, validation and/or post step. + @{Actions (i.e. named registered commands) to run on pre, validation and/or post step. These are independent of daemons and are executed at time of processing each service.} - 'commands' ? ${project.artifactId}_commands + 'actions' ? ${project.artifactId}_actions } = dict(); type ${project.artifactId}_component = { include structure_component 'services' : ${project.artifactId}_config{} with valid_absolute_file_paths(SELF) + @{Command registry for allowed actions, keys should be used as action value} + 'commands' ? string{} +} with { + foreach (esc_fn; srv; SELF['services']) { + if (exists(srv['actions'])) { + foreach (action; cmd_ref; srv['actions']) { + if (!(exists(SELF['commands']) && exists(SELF['commands'][cmd_ref]))) { + error('Found %s action %s for %s, but no matching command registered', + action, cmd_ref, unescape(esc_fn)); + }; + }; + }; + }; + true; }; diff --git a/ncm-metaconfig/src/main/perl/metaconfig.pm b/ncm-metaconfig/src/main/perl/metaconfig.pm index eeeaa82cf2..466195c37e 100644 --- a/ncm-metaconfig/src/main/perl/metaconfig.pm +++ b/ncm-metaconfig/src/main/perl/metaconfig.pm @@ -156,16 +156,16 @@ use EDG::WP4::CCM::Path qw(unescape); use Readonly; # Has to correspond to what is allowed in the schema -Readonly::Hash my %ALLOWED_ACTIONS => { restart => 1, reload => 1, stop_sleep_start => 1 }; +Readonly::Hash my %ALLOWED_DAEMON_ACTIONS => { restart => 1, reload => 1, stop_sleep_start => 1 }; our $EC = LC::Exception::Context->new->will_store_all; our $NoActionSupported = 1; # Given metaconfigservice C<$srv> for C<$file> and hash-reference C<$actions>, -# prepare the actions to be taken for this service/file. +# prepare the srv_actions to be taken for this service/file. # C is updated in-place; does not return anything. -sub prepare_action +sub prepare_daemon_action { my ($self, $srv, $file, $actions) = @_; @@ -190,8 +190,8 @@ sub prepare_action } my @acts; - while (my ($daemon,$action) = splice(@daemon_action, 0, 2)) { - if (exists($ALLOWED_ACTIONS{$action})) { + while (my ($daemon, $action) = splice(@daemon_action, 0, 2)) { + if (exists($ALLOWED_DAEMON_ACTIONS{$action})) { $actions->{$action} ||= {}; $actions->{$action}->{$daemon} = 1; push(@acts, "$daemon:$action"); @@ -211,7 +211,7 @@ sub prepare_action # Take the action for all daemons as defined in hash-reference C<$actions>. # Does not return anything. -sub process_actions +sub process_daemon_actions { my ($self, $actions) = @_; foreach my $action (sort keys %$actions) { @@ -224,6 +224,7 @@ sub process_actions } # Run $service shell command of $type (ie a string) (if defined). +# If $commands is undefined, nothing will be run or logged # $msg is a reporting prefix # When $input is not undef, pass it on stdin # Return 1 on success, undef otherwise. @@ -231,6 +232,8 @@ sub run_shell_command { my ($self, $commands, $type, $input) = @_; + return 1 if ! defined($commands); + my $command = $commands->{$type}; if ($command) { $self->debug(1, "Going to run $type command '$command'"); @@ -269,23 +272,25 @@ sub run_shell_command # contents C<$contents> (if C<$contents> is not defined, # C<$srv->{contents}> is used). # Also tracks the actions that need to be taken via the -# C<$actions> hash-reference. +# C<$daemon_actions> hash-reference. +# C<$commands> is a hashref with pre/test/changed/post action commands. +# (If it is undefined, nothing will be run or logged, see run_shell_command) # Returns undef in case of rendering or other failure, 1 otherwise. sub handle_service { - my ($self, $file, $srv, $contents, $actions) = @_; + my ($self, $file, $srv, $contents, $daemon_actions, $commands) = @_; - my $commands = $srv->{commands} || {}; return if ! $self->run_shell_command($commands, 'pre'); $contents = $srv->{contents} if (! defined($contents)); - my $trd = EDG::WP4::CCM::TextRender->new($srv->{module}, - $contents, - log => $self, - eol => 0, - element => $srv->{convert}, - ); + my $trd = EDG::WP4::CCM::TextRender->new( + $srv->{module}, + $contents, + log => $self, + eol => 0, + element => $srv->{convert}, + ); my %opts = ( log => $self, @@ -317,7 +322,7 @@ sub handle_service if ($fh->close()) { $self->info("File $file updated"); - $self->prepare_action($srv, $file, $actions); + $self->prepare_daemon_action($srv, $file, $daemon_actions); return if ! $self->run_shell_command($commands, 'changed'); } else { $self->verbose("File $file up-to-date"); @@ -326,46 +331,73 @@ sub handle_service return $self->run_shell_command($commands, 'post'); } +# Lookup actions in command registry, and return hashref with actual commands for each action +sub resolve_command_actions +{ + my ($self, $command_registry, $actions) = @_; + + my $commands = {}; # this will trigger reporting that nothing is configured is this stays empty + foreach my $type (sort keys %$actions) { + my $action = $actions->{$type}; + my $command = $command_registry->{$action}; + if ($command) { + $commands->{$type} = $command; + $self->verbose("Resolved $type action $action to command '$command'"); + } else { + # Not fatal, should be covered in schema already + $self->error("Unable to resovle $type action $action to command"); + } + }; + return $commands; +} + sub _configure_files { - my ($self, $config, $root) = @_; + my ($self, $config, %opts) = @_; + + my $root = defined($opts{root}) ? $opts{root} : ''; + my $run_commands = defined($opts{run_commands}) ? $opts{run_commands} : 1; my $t = $config->getElement($self->prefix)->getTree(); - my $actions = {}; + my $daemon_actions = {}; foreach my $esc_filename (sort keys %{$t->{services}}) { my $srvc = $t->{services}->{$esc_filename}; my $cont_el = $config->getElement($self->prefix()."/services/$esc_filename/contents"); - my $filename = ($root || '') . unescape($esc_filename); - $self->handle_service($filename, $srvc, $cont_el, $actions); + my $filename = $root . unescape($esc_filename); + + # Only when run_commands is false, use undef so nothing is even reported + my $commands = $run_commands ? $self->resolve_command_actions($t->{commands}, $srvc->{actions} || {}) : undef; + + $self->handle_service($filename, $srvc, $cont_el, $daemon_actions, $commands); } - return $actions; + return $daemon_actions; } sub Configure { my ($self, $config) = @_; - my $actions = $self->_configure_files($config); + my $daemon_actions = $self->_configure_files($config); - $self->process_actions($actions); + $self->process_daemon_actions($daemon_actions); return 1; } # Generate the files relative to metaconfig subdirectory # under the configuration cachemanager cache path. -# No daemons will be restarted. +# No daemons will be restarted, no commands run. sub aii_command { my ($self, $config) = @_; my $root = $config->{cache_path}; if ($root) { - $self->_configure_files($config, "$root/metaconfig"); + $self->_configure_files($config, root => "$root/metaconfig", run_commands => 0); return 1; } else { $self->error("No cache_path found for Configuration instance"); diff --git a/ncm-metaconfig/src/test/perl/aii_command.t b/ncm-metaconfig/src/test/perl/aii_command.t index 66b97df8b0..1f9ac3af78 100644 --- a/ncm-metaconfig/src/test/perl/aii_command.t +++ b/ncm-metaconfig/src/test/perl/aii_command.t @@ -33,6 +33,6 @@ isa_ok($fh, "CAF::FileWriter"); $fh = get_file("/foo/bar"); ok(!defined($fh), "Nothing created at regular file location"); -ok(command_history_ok(undef, ['service foo']), "serivce foo not restarted"); +ok(command_history_ok(undef, ['service foo', 'cmd']), "serivce foo not restarted, no cmd run"); done_testing(); diff --git a/ncm-metaconfig/src/test/resources/aii.pan b/ncm-metaconfig/src/test/resources/aii.pan index b72c1cbec8..9a5dfccef1 100644 --- a/ncm-metaconfig/src/test/resources/aii.pan +++ b/ncm-metaconfig/src/test/resources/aii.pan @@ -1,3 +1,3 @@ object template aii; -include 'simple'; +include 'simple_commands'; diff --git a/ncm-metaconfig/src/test/resources/commands.pan b/ncm-metaconfig/src/test/resources/commands.pan index b6c8f8c4c6..e1444cb5fc 100644 --- a/ncm-metaconfig/src/test/resources/commands.pan +++ b/ncm-metaconfig/src/test/resources/commands.pan @@ -1,9 +1,3 @@ object template commands; -include 'simple'; - -prefix "/software/components/metaconfig/services/{/foo/bar}/commands"; -"pre" = "cmd pre"; -"test" = "cmd test"; -"changed" = "cmd changed"; -"post" = "cmd post"; +include 'simple_commands';