Blob Blame History Raw
From 16d3d30130215d74295e89ba5a51522eed45e180 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Wed, 1 Feb 2012 14:20:53 +0200
Subject: [PATCH 1/3] Add management of inifiles to allow manipulation of
 systemd units

inifile_replace_variables() works similar to config_replace_variables() but
allows to apply changes to specific section of an inifile. Inifiles are
commonly used by freedesktop.org software and particularly used by systemd.

When modifying inifile, all changes will be applied to specific section.

Also fixes corner case in config_replace_variables() which would dublicate
variables when adding them.
---
 ipapython/ipautil.py |  100 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 99 insertions(+), 1 deletions(-)

diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index 718f209b32649df23177dcab7d5105d01c0cd7bc..e141e00171cb86bec58a6be0b3e7d1f51a24faf1 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -1245,7 +1245,7 @@ $)''', re.VERBOSE)
         new_vars = replacevars.copy()
         new_vars.update(appendvars)
         newvars_view = set(new_vars.keys()) - set(old_values.keys())
-        append_view = (set(appendvars.keys()) - set(replacevars.keys())) - set(old_values.keys())
+        append_view = (set(appendvars.keys()) - newvars_view)
         for item in newvars_view:
             new_config.write("%s=%s\n" % (item,new_vars[item]))
         for item in append_view:
@@ -1262,6 +1262,104 @@ $)''', re.VERBOSE)
 
     return old_values
 
+def inifile_replace_variables(filepath, section, replacevars=dict(), appendvars=dict()):
+    """
+    Take a section-structured key=value based configuration file, and write new version
+    with certain values replaced or appended within the section
+
+    All (key,value) pairs from replacevars and appendvars that were not found
+    in the configuration file, will be added there.
+
+    It is responsibility of a caller to ensure that replacevars and
+    appendvars do not overlap.
+
+    It is responsibility of a caller to back up file.
+
+    returns dictionary of affected keys and their previous values
+
+    One have to run restore_context(filepath) afterwards or
+    security context of the file will not be correct after modification
+    """
+    pattern = re.compile('''
+(^
+                        \[
+        (?P<section>    .+) \]
+                        (\s+((\#|;).*)?)?
+$)|(^
+                        \s*
+        (?P<option>     [^\#;]+?)
+                        (\s*=\s*)
+        (?P<value>      .+?)?
+                        (\s*((\#|;).*)?)?
+$)''', re.VERBOSE)
+    def add_options(config, replacevars, appendvars, oldvars):
+        # add all options from replacevars and appendvars that were not found in the file
+        new_vars = replacevars.copy()
+        new_vars.update(appendvars)
+        newvars_view = set(new_vars.keys()) - set(oldvars.keys())
+        append_view = (set(appendvars.keys()) - newvars_view)
+        for item in newvars_view:
+            config.write("%s=%s\n" % (item,new_vars[item]))
+        for item in append_view:
+            config.write("%s=%s\n" % (item,appendvars[item]))
+
+    orig_stat = os.stat(filepath)
+    old_values = dict()
+    temp_filename = None
+    with tempfile.NamedTemporaryFile(delete=False) as new_config:
+        temp_filename = new_config.name
+        with open(filepath, 'r') as f:
+            in_section = False
+            finished = False
+            line_idx = 1
+            for line in f:
+                line_idx = line_idx + 1
+                new_line = line
+                m = pattern.match(line)
+                if m:
+                    sect, option, value = m.group('section', 'option', 'value')
+                    if in_section and sect is not None:
+                        # End of the searched section, add remaining options
+                        add_options(new_config, replacevars, appendvars, old_values)
+                        finished = True
+                    if sect is not None:
+                        # New section is found, check whether it is the one we are looking for
+                        in_section = (str(sect).lower() == str(section).lower())
+                    if option is not None and in_section:
+                        # Great, this is an option from the section we are loking for
+                        if replacevars and option in replacevars:
+                            # replace value completely
+                            new_line = u"%s=%s\n" % (option, replacevars[option])
+                            old_values[option] = value
+                        if appendvars and option in appendvars:
+                            # append a new value unless it is already existing in the original one
+                            if not value:
+                                new_line = u"%s=%s\n" % (option, appendvars[option])
+                            elif value.find(appendvars[option]) == -1:
+                                new_line = u"%s=%s %s\n" % (option, value, appendvars[option])
+                            old_values[option] = value
+                    new_config.write(new_line)
+            # We have finished parsing the original file.
+            # There are two remaining cases:
+            # 1. Section we were looking for was not found, we need to add it.
+            if not (in_section or finished):
+                new_config.write("[%s]\n" % (section))
+            # 2. The section is the last one but some options were not found, add them.
+            if in_section or not finished:
+                add_options(new_config, replacevars, appendvars, old_values)
+
+        new_config.flush()
+        # Make sure the resulting file is readable by others before installing it
+        os.fchmod(new_config.fileno(), orig_stat.st_mode)
+        os.fchown(new_config.fileno(), orig_stat.st_uid, orig_stat.st_gid)
+
+    # At this point new_config is closed but not removed due to 'delete=False' above
+    # Now, install the temporary file as configuration and ensure old version is available as .orig
+    # While .orig file is not used during uninstall, it is left there for administrator.
+    install_file(temp_filename, filepath)
+
+    return old_values
+
 def backup_config_and_replace_variables(fstore, filepath, replacevars=dict(), appendvars=dict()):
     """
     Take a key=value based configuration file, back up it, and
-- 
1.7.8.3