commit:     6e86186244d048e3edd5c11c18cfb4eee98a0d56
Author:     Florian Schmaus <flo <AT> geekplace <DOT> eu>
AuthorDate: Sun Mar 28 12:55:04 2021 +0000
Commit:     Michał Górny <mgorny <AT> gentoo <DOT> org>
CommitDate: Sun Aug 22 15:32:07 2021 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=6e861862

dispatch-conf: Add support for conf-update.d hook directory

Those hooks can be used by tools that manage /etc to get notified
about updated configuration files. For example, etckeeper could hook
this mechanism like the following:

/etc/portage/conf-update.d/etckeeper
case "${1}" in
    pre-update)
        etckeeper pre-install
    ;;
    post-update)
        etckeeper post-install
    ;;
esac

Currently conf-update.d hooks are called with 4 different events:
- pre-session
- post-session
- pre-update
- post-update

The *-session events are emitted prior starting a new configuration
update sesssion, and when it is finished. That is, the pre-session
event is emitted just before dispatch-conf displays the first
configuration file, and right before it exists.

The *-update events are emitted before and after a configuration file
has been updated. The path of the configuration file is provided as
second hook argument.

Signed-off-by: Florian Schmaus <flo <AT> geekplace.eu>
Closes: https://bugs.gentoo.org/698316
Bug: https://bugs.gentoo.org/260623
Closes: https://github.com/gentoo/portage/pull/689
Signed-off-by: Michał Górny <mgorny <AT> gentoo.org>

 bin/dispatch-conf            | 13 ++++++++++++-
 lib/portage/dispatch_conf.py | 14 ++++++++++++++
 lib/portage/util/hooks.py    | 12 ++++++++++++
 man/dispatch-conf.1          |  5 +++++
 4 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/bin/dispatch-conf b/bin/dispatch-conf
index 0fdfbaa81..f36815a52 100755
--- a/bin/dispatch-conf
+++ b/bin/dispatch-conf
@@ -34,7 +34,8 @@ import portage
 portage._internal_caller = True
 from portage import os, shutil
 from portage import _encodings, _unicode_decode
-from portage.dispatch_conf import diffstatusoutput, diff_mixed_wrapper
+from portage.dispatch_conf import (diffstatusoutput, diff_mixed_wrapper,
+       perform_conf_update_hooks, perform_conf_update_session_hooks)
 from portage.process import find_binary, spawn
 from portage.util import writemsg, writemsg_stdout
 
@@ -97,6 +98,8 @@ class dispatch:
         confs = []
         count = 0
 
+        perform_conf_update_session_hooks("pre-session")
+
         config_root = portage.settings["EPREFIX"] or os.sep
         self.options = portage.dispatch_conf.read_config(MANDATORY_OPTS)
 
@@ -317,6 +320,7 @@ class dispatch:
                         break
 
                 if c == 'q':
+                    perform_conf_update_session_hooks("post-session")
                     sys.exit (0)
                 if c == 'h':
                     self.do_help ()
@@ -378,6 +382,8 @@ class dispatch:
                 writemsg_stdout("  * '%s'\n" % frozen, noiselevel=-1)
             print()
 
+        perform_conf_update_session_hooks("post-session")
+
     def replace (self, newconf, curconf):
         """Replace current config with the new/merged version.  Also logs
         the diff of what changed into the configured log file."""
@@ -387,11 +393,16 @@ class dispatch:
                 encoding=_encodings["stdio"]) as f:
                 f.write(output + "\n")
 
+        perform_conf_update_hooks("pre-update", curconf)
+
         try:
             os.rename(newconf, curconf)
         except (IOError, os.error) as why:
             writemsg('dispatch-conf: Error renaming %s to %s: %s; fatal\n' % \
                   (newconf, curconf, str(why)), noiselevel=-1)
+            return
+
+        perform_conf_update_hooks("post-update", curconf)
 
 
     def post_process(self, curconf):

diff --git a/lib/portage/dispatch_conf.py b/lib/portage/dispatch_conf.py
index 3ef659852..851e6c747 100644
--- a/lib/portage/dispatch_conf.py
+++ b/lib/portage/dispatch_conf.py
@@ -384,3 +384,17 @@ def file_archive_post_process(archive):
                if os.path.isdir(dest) and not os.path.islink(dest):
                        _file_archive_rotate(dest)
                os.rename(archive + '.dist.new', dest)
+
+
+def perform_conf_update_hooks(kind, conf):
+       """Invoke the hooks in the conf-update.d directory. The notification
+       'kind' must either be 'pre-update' or 'post-update'. And 'conf' is the
+       absolute path to the configuration file that is going to be or was
+       updated."""
+       perform_hooks("conf-update.d", kind, conf)
+
+
+def perform_conf_update_session_hooks(kind):
+       """Invoke the hooks in the conf-update-session.d directory. The
+       notification 'kind' must either be 'pre-session' or 'post-session'."""
+       perform_hooks("conf-update.d", kind)

diff --git a/lib/portage/util/hooks.py b/lib/portage/util/hooks.py
index d10ec7a59..942b15543 100644
--- a/lib/portage/util/hooks.py
+++ b/lib/portage/util/hooks.py
@@ -12,6 +12,7 @@ from portage.output import create_color_func
 from portage.util import writemsg_level, _recursive_file_list
 from warnings import warn
 
+bad = create_color_func("BAD")
 warn = create_color_func("WARN")
 
 
@@ -29,3 +30,14 @@ def get_hooks_from_dir(rel_directory, prefix="/"):
                                level=logging.WARN, noiselevel=2)
 
        return hooks
+
+
+def perform_hooks(rel_directory, *argv, prefix="/"):
+       for filepath, name in get_hooks_from_dir(rel_directory, prefix).items():
+               hook_command = filepath + " " +  " ".join(map(str, argv))
+               retval = portage.process.spawn(hook_command)
+
+               if retval != portage.os.EX_OK:
+                       writemsg_level(" %s Spawn failed for: %s, %s\n" % \
+                               (bad("*"), name, filepath),
+                               level=logging.ERROR, noiselevel=-1)

diff --git a/man/dispatch-conf.1 b/man/dispatch-conf.1
index a3d233bc0..b877b6942 100644
--- a/man/dispatch-conf.1
+++ b/man/dispatch-conf.1
@@ -70,6 +70,11 @@ older permissions of the first check in may be inherited. As
 mentioned in the \fBci\fR(1) man page, users can control access
 to RCS files by setting the permissions of the directory
 containing the files.
+.SH "CONF-UPDATE HOOKS"
+\fIdispatch\-conf\fR will run hooks in \fB/etc/portage/conf-update.d\fR.
+The first argument of the hook is either \fIpre-session\fR, \fIpost-ression\fR,
+\fIpre-update\fR, or, \fIpost-update\fR. In case of *-update events, a second
+argument containing the path of the configuration file is also provided.
 .SH "REPORTING BUGS"
 Please report bugs via https://bugs.gentoo.org/
 .SH "AUTHORS"

Reply via email to