summaryrefslogtreecommitdiffstats
path: root/ipa-server/ipaserver/sysrestore.py
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2008-01-14 12:43:26 -0500
committerRob Crittenden <rcritten@redhat.com>2008-01-14 12:43:26 -0500
commitc7f3c746ccfd74480064dbe73fbc754548c30927 (patch)
treee0e8296dd50c0f9815f930713a2b3e917cb83bdf /ipa-server/ipaserver/sysrestore.py
parent23ac773ada10867767e779823ab5f66c79b0dc04 (diff)
downloadfreeipa-c7f3c746ccfd74480064dbe73fbc754548c30927.tar.gz
freeipa-c7f3c746ccfd74480064dbe73fbc754548c30927.tar.xz
freeipa-c7f3c746ccfd74480064dbe73fbc754548c30927.zip
Backup system state in ipa-server-install
This patch adds a sysrestore module which allows ipa-server-install code to backup any system state so that it can be restored again with e.g. ipa-server-install --uninstall. The idea is that any files ipa-server-install modifies gets backed up to /var/cache/ipa/sysrestore/ while any "meta" state, like whether a service is enabled with chkconfig, is saved to /var/cache/ipa/sysrestore.state. Signed-off-by: Mark McLoughlin <markmc@redhat.com>
Diffstat (limited to 'ipa-server/ipaserver/sysrestore.py')
-rw-r--r--ipa-server/ipaserver/sysrestore.py253
1 files changed, 253 insertions, 0 deletions
diff --git a/ipa-server/ipaserver/sysrestore.py b/ipa-server/ipaserver/sysrestore.py
new file mode 100644
index 000000000..4716aa0da
--- /dev/null
+++ b/ipa-server/ipaserver/sysrestore.py
@@ -0,0 +1,253 @@
+# Authors: Mark McLoughlin <markmc@redhat.com>
+#
+# Copyright (C) 2007 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; version 2 or later
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+#
+# This module provides a very simple API which allows
+# ipa-server-install --uninstall to restore certain
+# parts of the system configuration to the way it was
+# before ipa-server-install was first run
+#
+
+import os
+import os.path
+import errno
+import shutil
+import logging
+import ConfigParser
+
+from ipa import ipautil
+
+SYSRESTORE_CACHE_PATH = "/var/cache/ipa/sysrestore"
+SYSRESTORE_STATEFILE_PATH = "/var/cache/ipa/sysrestore.state"
+
+def _mktree(basedir, reldir):
+ """Create the tree of directories specified by @reldir
+ under the directory @base.
+
+ Caveats:
+ - @basedir must exist
+ - @reldir must not be absolute
+ - @reldir must refer to a directory
+ """
+ (parentdir, subdir) = os.path.split(reldir)
+ if parentdir:
+ _mktree(basedir, parentdir)
+
+ absdir = os.path.join(basedir, reldir)
+ try:
+ logging.debug("Creating directory '%s'", absdir)
+ os.mkdir(absdir)
+ except OSError, err:
+ if err.errno != errno.EEXIST:
+ raise err
+
+def _rmtree(basedir, reldir):
+ """Delete a tree of directories specified by @reldir
+ under the directory @base, excluding the @base itself.
+ Only empty directories will be deleted.
+
+ Caveats:
+ - @reldir must not be absolute
+ - @reldir must refer to a directory
+ """
+ absdir = os.path.join(basedir, reldir)
+ try:
+ logging.debug("Deleting directory '%s'", absdir)
+ os.rmdir(absdir)
+ except OSError, err:
+ if err.errno == errno.ENOTEMPTY:
+ logging.debug("Directory '%s' not empty", absdir)
+ return
+ else:
+ raise err
+
+ (parentdir, subdir) = os.path.split(reldir)
+ if parentdir:
+ _rmtree(basedir, parentdir)
+
+def backup_file(path):
+ """Create a copy of the file at @path - so long as a copy
+ does not already exist - which will be restored to its
+ original location by restore_files().
+ """
+ logging.debug("Backing up system configuration file '%s'", path)
+
+ if not os.path.isabs(path):
+ raise ValueError("Absolute path required")
+
+ if not os.path.isfile(path):
+ logging.debug(" -> Not backing up - '%s' doesn't exist", path)
+ return
+
+ relpath = path[1:]
+
+ backup_path = os.path.join(SYSRESTORE_CACHE_PATH, relpath)
+ if os.path.exists(backup_path):
+ logging.debug(" -> Not backing up - already have a copy of '%s'", path)
+ return
+
+ (reldir, file) = os.path.split(relpath)
+ if reldir:
+ _mktree(SYSRESTORE_CACHE_PATH, reldir)
+
+ shutil.copy2(path, backup_path)
+
+def restore_file(path):
+ """Restore the copy of a file at @path to its original
+ location and delete the copy.
+
+ Returns #True if the file was restored, #False if there
+ was no backup file to restore
+ """
+ logging.debug("Restoring system configuration file '%s'", path)
+
+ if not os.path.isabs(path):
+ raise ValueError("Absolute path required")
+
+ relpath = path[1:]
+
+ backup_path = os.path.join(SYSRESTORE_CACHE_PATH, relpath)
+ if not os.path.exists(backup_path):
+ logging.debug(" -> Not restoring - '%s' doesn't exist", backup_path)
+ return False
+
+ shutil.move(backup_path, path)
+
+ ipautil.run(["/sbin/restorecon", path])
+
+ (reldir, file) = os.path.split(relpath)
+ if reldir:
+ _rmtree(SYSRESTORE_CACHE_PATH, reldir)
+
+ return True
+
+class _StateFile:
+ """A metadata file for recording system state which can
+ be backed up and later restored. The format is something
+ like:
+
+ [httpd]
+ running=True
+ enabled=False
+ """
+
+ def __init__(self, path = SYSRESTORE_STATEFILE_PATH):
+ """Create a _StateFile object, loading from @path.
+
+ The dictionary @modules, a member of the returned object,
+ is where the state can be modified. @modules is indexed
+ using a module name to return another dictionary containing
+ key/value pairs with the saved state of that module.
+
+ The keys in these latter dictionaries are arbitrary strings
+ and the values may either be strings or booleans.
+ """
+ self._path = path
+
+ self.modules = {}
+
+ self._load()
+
+ def _load(self):
+ """Load the modules from the file @_path. @modules will
+ be an empty dictionary if the file doesn't exist.
+ """
+ logging.debug("Loading StateFile from '%s'", self._path)
+
+ self.modules = {}
+
+ p = ConfigParser.SafeConfigParser()
+ p.read(self._path)
+
+ for module in p.sections():
+ self.modules[module] = {}
+ for (key, value) in p.items(module):
+ if value == str(True):
+ value = True
+ elif value == str(False):
+ value = False
+ self.modules[module][key] = value
+
+ def save(self):
+ """Save the modules to @_path. If @modules is an empty
+ dict, then @_path should be removed.
+ """
+ logging.debug("Saving StateFile to '%s'", self._path)
+
+ for module in self.modules.keys():
+ if len(self.modules[module]) == 0:
+ del self.modules[module]
+
+ if len(self.modules) == 0:
+ logging.debug(" -> no modules, removing file")
+ if os.path.exists(self._path):
+ os.remove(self._path)
+ return
+
+ p = ConfigParser.SafeConfigParser()
+
+ for module in self.modules.keys():
+ p.add_section(module)
+ for (key, value) in self.modules[module].items():
+ p.set(module, key, str(value))
+
+ f = file(self._path, "w")
+ p.write(f)
+ f.close()
+
+def backup_state(module, key, value):
+ """Backup an item of system state from @module, identified
+ by the string @key and with the value @value. @value may be
+ a string or boolean.
+ """
+ if not (isinstance(value, str) or isinstance(value, bool)):
+ raise ValueError("Only strings or booleans supported")
+
+ state = _StateFile()
+
+ if not state.modules.has_key(module):
+ state.modules[module] = {}
+
+ if not state.modules.has_key(key):
+ state.modules[module][key] = value
+
+ state.save()
+
+def restore_state(module, key):
+ """Return the value of an item of system state from @module,
+ identified by the string @key, and remove it from the backed
+ up system state.
+
+ If the item doesn't exist, #None will be returned, otherwise
+ the original string or boolean value is returned.
+ """
+ state = _StateFile()
+
+ if not state.modules.has_key(module):
+ return None
+
+ if not state.modules[module].has_key(key):
+ return None
+
+ value = state.modules[module][key]
+ del state.modules[module][key]
+
+ state.save()
+
+ return value