[rhel7-branch 2/2] Add simple_replace config file function (#1165980)

bcl installerbot-noreply at redhat.com
Wed Jun 17 00:17:39 UTC 2015


From: "Brian C. Lane" <bcl at redhat.com>

When modifying values in config files we want to preserve the rest of
the file as is. SimpleConfig does this for shell-like files using
upper-case KEYS, but other files (eg. INI style) don't use the same
format.

simple_replace takes a list of tuples (key, string) and replaces every
line in the file that starts with the key with the string. It is case
sensitive and the string must be the whole line including key.

eg.

keys=[("switch_1", "switch_1=On")]
simple_replace("/etc/control.conf", keys)

By default if a key isn't replaced in the existing file its string will
be appended to the end of the file. Set add=False to disable this behavior.

Related: rhbz#1165980
---
 pyanaconda/simpleconfig.py                  | 68 +++++++++++++++++++++++------
 tests/pyanaconda_tests/simpleconfig_test.py | 45 ++++++++++++++++++-
 2 files changed, 98 insertions(+), 15 deletions(-)

diff --git a/pyanaconda/simpleconfig.py b/pyanaconda/simpleconfig.py
index 47b7a95..b4d9a38 100644
--- a/pyanaconda/simpleconfig.py
+++ b/pyanaconda/simpleconfig.py
@@ -1,7 +1,7 @@
 #
 # simpleconifg.py - representation of a simple configuration file (sh-like)
 #
-# Copyright (C) 1999-2014 Red Hat, Inc.
+# Copyright (C) 1999-2015 Red Hat, Inc.
 #
 # This copyrighted material is made available to anyone wishing to use,
 # modify, copy, or redistribute it subject to the terms and conditions of
@@ -67,6 +67,22 @@ def find_comment(s):
     return None
 
 
+def write_tmpfile(filename, data):
+    tmpf = tempfile.NamedTemporaryFile(mode="w", delete=False)
+    tmpf.write(data)
+    tmpf.close()
+
+    # Move the temporary file (with 0600 permissions) over the top of the
+    # original and preserve the original's permissions
+    filename = os.path.realpath(filename)
+    if os.path.exists(filename):
+        m = os.stat(filename).st_mode
+    else:
+        m = int('0100644', 8)
+    shutil.move(tmpf.name, filename)
+    eintr_retry_call(os.chmod, filename, m)
+
+
 class SimpleConfigFile(object):
     """ Edit values in a configuration file without changing comments.
         Supports KEY=VALUE lines and ignores everything else.
@@ -109,19 +125,7 @@ def write(self, filename=None, use_tmp=True):
             return None
 
         if use_tmp:
-            tmpf = tempfile.NamedTemporaryFile(mode="w", delete=False)
-            tmpf.write(str(self))
-            tmpf.close()
-
-            # Move the temporary file (with 0600 permissions) over the top of the
-            # original and preserve the original's permissions
-            filename = os.path.realpath(filename)
-            if os.path.exists(filename):
-                m = os.stat(filename).st_mode
-            else:
-                m = int('0100644', 8)
-            shutil.move(tmpf.name, filename)
-            eintr_retry_call(os.chmod, filename, m)
+            write_tmpfile(filename, str(self))
         else:
             # write directly to the file
             with open(filename, "w") as fobj:
@@ -198,3 +202,39 @@ def __str__(self):
                 s += self._kvpair(key)
 
         return s
+
+
+def simple_replace(fname, keys, add=True, add_comment="# Added by Anaconda"):
+    """ Replace lines in a file, optionally adding if missing.
+
+    :param str fname: Filename to operate on
+    :param list keys: List of (key, string) tuples to search and replace
+    :param bool add: When True add strings that were not replaced
+
+    This will read all the lines in a file, looking for ones that start
+    with keys and replacing the line with the associated string. The string
+    should be a COMPLETE replacement for the line, not just a value.
+
+    When add is True any keys that haven't been found will be appended
+    to the end of the file along with the add_comment.
+    """
+    # Helper to return the line or the first matching key's string
+    def _replace(l):
+        r = [s for k,s in keys if l.startswith(k)]
+        if r:
+            return r[0]
+        else:
+            return l
+
+    # Replace lines that match any of the keys
+    with open(fname, "r") as f:
+        lines = [_replace(l.strip()) for l in f]
+
+    # Add any strings that weren't already in the file
+    if add:
+        append = [s for k,s in keys if not any(l.startswith(k) for l in lines)]
+        if append:
+            lines += [add_comment]
+            lines += append
+
+    write_tmpfile(fname, "\n".join(lines)+"\n")
diff --git a/tests/pyanaconda_tests/simpleconfig_test.py b/tests/pyanaconda_tests/simpleconfig_test.py
index 3719ec1..cca7c5b 100644
--- a/tests/pyanaconda_tests/simpleconfig_test.py
+++ b/tests/pyanaconda_tests/simpleconfig_test.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2014  Red Hat, Inc.
+# Copyright (C) 2014-2015 Red Hat, Inc.
 #
 # This copyrighted material is made available to anyone wishing to use,
 # modify, copy, or redistribute it subject to the terms and conditions of
@@ -19,6 +19,7 @@
 # Red Hat Author(s): Brian C. Lane <bcl at redhat.com>
 
 from pyanaconda.simpleconfig import SimpleConfigFile
+from pyanaconda.simpleconfig import simple_replace
 from pyanaconda import simpleconfig
 import unittest
 import tempfile
@@ -128,3 +129,45 @@ def remove_key_test(self):
             scf.reset()
             scf.read(testconfig.name)
             self.assertEqual(scf.get("BOOT"), "")
+
+class SimpleReplaceTests(unittest.TestCase):
+    TEST_CONFIG = """#SKIP=Skip this commented line
+BOOT=always
+"""
+
+    def replace_test(self):
+        with tempfile.NamedTemporaryFile() as testconfig:
+            testconfig.write(self.TEST_CONFIG)
+            testconfig.flush()
+
+            keys = [("BOOT", "BOOT=never")]
+            simple_replace(testconfig.name, keys)
+
+            config = SimpleConfigFile(testconfig.name)
+            config.read()
+            self.assertEqual(config.get("BOOT"), "never")
+
+    def append_test(self):
+        with tempfile.NamedTemporaryFile() as testconfig:
+            testconfig.write(self.TEST_CONFIG)
+            testconfig.flush()
+
+            keys = [("NEWKEY", "NEWKEY=froboz")]
+            simple_replace(testconfig.name, keys)
+
+            config = SimpleConfigFile(testconfig.name)
+            config.read()
+            self.assertEqual(config.get("NEWKEY"), "froboz")
+
+    def no_append_test(self):
+        with tempfile.NamedTemporaryFile() as testconfig:
+            testconfig.write(self.TEST_CONFIG)
+            testconfig.flush()
+
+            keys = [("BOOT", "BOOT=sometimes"), ("NEWKEY", "NEWKEY=froboz")]
+            simple_replace(testconfig.name, keys, add=False)
+
+            config = SimpleConfigFile(testconfig.name)
+            config.read()
+            self.assertEqual(config.get("BOOT"), "sometimes")
+            self.assertEqual(config.get("NEWKEY"), "")


-- 
To view this commit on github, visit https://github.com/rhinstaller/anaconda/commit/1b7c054ca13bd3887fde1a1e889d2c4ae707e494


More information about the anaconda-patches mailing list