diff options
author | Alexander Bokovoy <abokovoy@redhat.com> | 2011-10-12 16:42:09 +0300 |
---|---|---|
committer | Rob Crittenden <rcritten@redhat.com> | 2011-10-11 18:21:41 -0400 |
commit | 8badce286f9e50384b68e0f5e77e314fd48363db (patch) | |
tree | 8f065a02331b8c55db197619f18f602cbcd25946 | |
parent | 5c10f66e4abd35dc24fe65d5916e429bee221093 (diff) | |
download | freeipa-8badce286f9e50384b68e0f5e77e314fd48363db.tar.gz freeipa-8badce286f9e50384b68e0f5e77e314fd48363db.tar.xz freeipa-8badce286f9e50384b68e0f5e77e314fd48363db.zip |
Refactor backup_and_replace_hostname() into a flexible config modification tool
backup_and_replace_hostname() was doing three things:
1. Given config file in 'key=value' style, replace value for a
specified key (HOSTNAME)
2. Backup original file and install a replacement
3. Restore original security context after editing
We have several more places where parts of the functionality are needed,
thus making two tools in ipapython.ipautil:
1. config_replace_variables(filepath, replacevars=dict(),
appendvars=dict())
Replaces or appends values to specified keys, adding new key=value
pairs if key was absent
2. backup_config_and_replace_variables(fstore, filepath,
replacevars=dict(),
appendvars=dict())
Backups config file and calls config_replace_variables()
A caller must handle security context after using these two tools.
In addition, as before, there is
ipapython.services.backup_and_replace_hostname() that uses
these common tools and restores security context after editing.
The code will be used extensively for systemd integration for Fedora 16.
Fixes:
https://fedorahosted.org/freeipa/ticket/1871
-rw-r--r-- | ipapython/ipautil.py | 90 | ||||
-rw-r--r-- | ipapython/platform/redhat.py | 47 |
2 files changed, 97 insertions, 40 deletions
diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py index 6e037926..23286980 100644 --- a/ipapython/ipautil.py +++ b/ipapython/ipautil.py @@ -1185,3 +1185,93 @@ def get_ipa_basedn(conn): return None +def config_replace_variables(filepath, replacevars=dict(), appendvars=dict()): + """ + Take a key=value based configuration file, and write new version + with certain values replaced or appended + + 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(''' +(^ + \s* + (?P<option> [^\#;]+?) + (\s*=\s*) + (?P<value> .+?)? + (\s*((\#|;).*)?)? +$)''', re.VERBOSE) + 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: + for line in f: + new_line = line + m = pattern.match(line) + if m: + option, value = m.group('option', 'value') + if option is not None: + 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 new value unless it is already existing in the original one + if 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) + # Now add all options from replacevars and appendvars that were not found in the file + new_vars = replacevars.copy() + new_vars.update(appendvars) + newvars_view = new_vars.viewkeys() - old_values.viewkeys() + append_view = (appendvars.viewkeys() - replacevars.viewkeys()) - old_values.viewkeys() + for item in newvars_view: + new_config.write("%s=%s\n" % (item,new_vars[item])) + for item in append_view: + new_config.write("%s=%s\n" % (item,appendvars[item])) + 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 + write new version with certain values replaced or appended + + 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. + + 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 + """ + # Backup original filepath + fstore.backup_file(filepath) + old_values = config_replace_variables(filepath, replacevars, appendvars) + + return old_values diff --git a/ipapython/platform/redhat.py b/ipapython/platform/redhat.py index 6bf8bf34..9825ddfd 100644 --- a/ipapython/platform/redhat.py +++ b/ipapython/platform/redhat.py @@ -133,48 +133,15 @@ def restore_context(filepath): ipautil.run(["/sbin/restorecon", filepath], raiseonerr=False) def backup_and_replace_hostname(fstore, statestore, hostname): - network_filename = "/etc/sysconfig/network" - # Backup original /etc/sysconfig/network - fstore.backup_file(network_filename) - hostname_pattern = re.compile(''' -(^ - \s* - (?P<option> [^\#;]+?) - (\s*=\s*) - (?P<value> .+?)? - (\s*((\#|;).*)?)? -$)''', re.VERBOSE) - temp_filename = None - with tempfile.NamedTemporaryFile(delete=False) as new_config: - temp_filename = new_config.name - with open(network_filename, 'r') as f: - for line in f: - new_line = line - m = hostname_pattern.match(line) - if m: - option, value = m.group('option', 'value') - if option is not None and option == 'HOSTNAME': - if value is not None and hostname != value: - new_line = u"HOSTNAME=%s\n" % (hostname) - statestore.backup_state('network', 'hostname', value) - new_config.write(new_line) - new_config.flush() - # Make sure the resulting file is readable by others before installing it - os.fchmod(new_config.fileno(), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH) - os.fchown(new_config.fileno(), 0, 0) - - # 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. - ipautil.install_file(temp_filename, network_filename) try: ipautil.run(['/bin/hostname', hostname]) except ipautil.CalledProcessError, e: print >>sys.stderr, "Failed to set this machine hostname to %s (%s)." % (hostname, str(e)) - - # For SE Linux environments it is important to reset SE labels to the expected ones - try: - restore_context(network_filename) - except ipautil.CalledProcessError, e: - print >>sys.stderr, "Failed to set permissions for %s (%s)." % (network_filename, str(e)) + replacevars = {'HOSTNAME':hostname} + old_values = ipautil.backup_config_and_replace_variables(fstore, + "/etc/sysconfig/network", + replacevars=replacevars) + restore_context("/etc/sysconfig/network") + if 'HOSTNAME' in old_values: + statestore.backup_state('network', 'hostname', old_values['HOSTNAME']) |