diff options
author | Chris St. Pierre <chris.a.st.pierre@gmail.com> | 2012-10-18 11:33:38 -0400 |
---|---|---|
committer | Chris St. Pierre <chris.a.st.pierre@gmail.com> | 2012-10-30 09:11:53 -0400 |
commit | 63a5b62dba190007634e1e0f2f834057b63aeafd (patch) | |
tree | beb6d51675d43fe8303aff62a3c9272e82e67520 /src/lib | |
parent | 7b9987d1835ff422e21f0b2f36c93d956192bf4b (diff) | |
download | bcfg2-63a5b62dba190007634e1e0f2f834057b63aeafd.tar.gz bcfg2-63a5b62dba190007634e1e0f2f834057b63aeafd.tar.bz2 bcfg2-63a5b62dba190007634e1e0f2f834057b63aeafd.zip |
added Git.Update RMI, ability to base bcfg2 VCS repo at a different directory than the repo root
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/Bcfg2/Options.py | 8 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugin/interfaces.py | 27 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Bzr.py | 10 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Cvs.py | 15 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Darcs.py | 12 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Fossil.py | 10 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Git.py | 143 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Hg.py | 13 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Svn.py | 133 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Svn2.py | 116 |
10 files changed, 286 insertions, 201 deletions
diff --git a/src/lib/Bcfg2/Options.py b/src/lib/Bcfg2/Options.py index 83234a792..f3765a5ec 100644 --- a/src/lib/Bcfg2/Options.py +++ b/src/lib/Bcfg2/Options.py @@ -572,6 +572,11 @@ SERVER_DAEMON_GROUP = \ default=0, cf=('server', 'group'), cook=get_gid) +SERVER_VCS_ROOT = \ + Option('Server VCS repository root', + default=None, + odesc='<VCS repository root>', + cf=('server', 'vcs_root')) # database options DB_ENGINE = \ @@ -1078,7 +1083,8 @@ SERVER_COMMON_OPTIONS = dict(repo=SERVER_REPOSITORY, ca=SERVER_CA, protocol=SERVER_PROTOCOL, web_configfile=WEB_CFILE, - backend=SERVER_BACKEND) + backend=SERVER_BACKEND, + vcs_root=SERVER_VCS_ROOT) CRYPT_OPTIONS = dict(encrypt=ENCRYPT, decrypt=DECRYPT, diff --git a/src/lib/Bcfg2/Server/Plugin/interfaces.py b/src/lib/Bcfg2/Server/Plugin/interfaces.py index 894165858..cba3e8145 100644 --- a/src/lib/Bcfg2/Server/Plugin/interfaces.py +++ b/src/lib/Bcfg2/Server/Plugin/interfaces.py @@ -506,7 +506,7 @@ class GoalValidator(object): raise NotImplementedError -class Version(object): +class Version(Plugin): """ Version plugins interact with various version control systems. """ #: The path to the VCS metadata file or directory, relative to the @@ -514,26 +514,23 @@ class Version(object): #: be ".svn" __vcs_metadata_path__ = None - def __init__(self, datastore): - """ - :param datastore: The path to the Bcfg2 repository on the - filesystem - :type datastore: string - :raises: :class:`Bcfg2.Server.Plugin.exceptions.PluginInitError` - - .. autoattribute:: __vcs_metadata_path__ - """ + def __init__(self, core, datastore): + Plugin.__init__(self, core, datastore) - self.datastore = datastore if self.__vcs_metadata_path__: - self.vcs_path = os.path.join(datastore, self.__vcs_metadata_path__) - - if os.path.exists(self.vcs_path): - self.get_revision() + if core.setup['vcs_root']: + self.vcs_root = core.setup['vcs_root'] else: + self.vcs_root = datastore + self.vcs_path = os.path.join(self.vcs_root, + self.__vcs_metadata_path__) + + if not os.path.exists(self.vcs_path): raise PluginInitError("%s is not present" % self.vcs_path) else: self.vcs_path = None + __init__.__doc__ = Plugin.__init__.__doc__ + """ +.. autoattribute:: __vcs_metadata_path__ """ def get_revision(self): """ Return the current revision of the Bcfg2 specification. diff --git a/src/lib/Bcfg2/Server/Plugins/Bzr.py b/src/lib/Bcfg2/Server/Plugins/Bzr.py index a8b4113ff..e0cbdf72a 100644 --- a/src/lib/Bcfg2/Server/Plugins/Bzr.py +++ b/src/lib/Bcfg2/Server/Plugins/Bzr.py @@ -6,23 +6,21 @@ from bzrlib.workingtree import WorkingTree from bzrlib import errors -class Bzr(Bcfg2.Server.Plugin.Plugin, - Bcfg2.Server.Plugin.Version): +class Bzr(Bcfg2.Server.Plugin.Version): """ The Bzr plugin provides a revision interface for Bcfg2 repos using bazaar. """ __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): - Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) - Bcfg2.Server.Plugin.Version.__init__(self, datastore) + Bcfg2.Server.Plugin.Version.__init__(self, core, datastore) self.logger.debug("Initialized Bazaar plugin with directory %s at " - "revision = %s" % (self.datastore, + "revision = %s" % (self.vcs_root, self.get_revision())) def get_revision(self): """Read Bazaar revision information for the Bcfg2 repository.""" try: - working_tree = WorkingTree.open(self.datastore) + working_tree = WorkingTree.open(self.vcs_root) revision = str(working_tree.branch.revno()) if (working_tree.has_changes(working_tree.basis_tree()) or working_tree.unknowns()): diff --git a/src/lib/Bcfg2/Server/Plugins/Cvs.py b/src/lib/Bcfg2/Server/Plugins/Cvs.py index a36a116f5..ba1559a1a 100644 --- a/src/lib/Bcfg2/Server/Plugins/Cvs.py +++ b/src/lib/Bcfg2/Server/Plugins/Cvs.py @@ -5,17 +5,15 @@ from subprocess import Popen, PIPE import Bcfg2.Server.Plugin -class Cvs(Bcfg2.Server.Plugin.Plugin, - Bcfg2.Server.Plugin.Version): +class Cvs(Bcfg2.Server.Plugin.Version): """ The Cvs plugin provides a revision interface for Bcfg2 repos using cvs.""" __author__ = 'bcfg-dev@mcs.anl.gov' __vcs_metadata_path__ = "CVSROOT" def __init__(self, core, datastore): - Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) - Bcfg2.Server.Plugin.Version.__init__(self, datastore) - self.logger.debug("Initialized cvs plugin with cvs directory %s" % + Bcfg2.Server.Plugin.Version.__init__(self, core, datastore) + self.logger.debug("Initialized cvs plugin with CVS directory %s" % self.vcs_path) def get_revision(self): @@ -23,11 +21,12 @@ class Cvs(Bcfg2.Server.Plugin.Plugin, try: data = Popen("env LC_ALL=C cvs log", shell=True, - cwd=self.datastore, + cwd=self.vcs_root, stdout=PIPE).stdout.readlines() return data[3].strip('\n') except IndexError: - msg = "Failed to read cvs log" + msg = "Failed to read CVS log" self.logger.error(msg) - self.logger.error('Ran command "cvs log %s"' % self.datastore) + self.logger.error('Ran command "cvs log" from directory %s' % + self.vcs_root) raise Bcfg2.Server.Plugin.PluginExecutionError(msg) diff --git a/src/lib/Bcfg2/Server/Plugins/Darcs.py b/src/lib/Bcfg2/Server/Plugins/Darcs.py index 9ec8e2df3..0033e00f3 100644 --- a/src/lib/Bcfg2/Server/Plugins/Darcs.py +++ b/src/lib/Bcfg2/Server/Plugins/Darcs.py @@ -5,16 +5,14 @@ from subprocess import Popen, PIPE import Bcfg2.Server.Plugin -class Darcs(Bcfg2.Server.Plugin.Plugin, - Bcfg2.Server.Plugin.Version): +class Darcs(Bcfg2.Server.Plugin.Version): """ Darcs is a version plugin for dealing with Bcfg2 repos stored in the Darcs VCS. """ __author__ = 'bcfg-dev@mcs.anl.gov' __vcs_metadata_path__ = "_darcs" def __init__(self, core, datastore): - Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) - Bcfg2.Server.Plugin.Version.__init__(self, datastore) + Bcfg2.Server.Plugin.Version.__init__(self, core, datastore) self.logger.debug("Initialized Darcs plugin with darcs directory %s" % self.vcs_path) @@ -23,13 +21,13 @@ class Darcs(Bcfg2.Server.Plugin.Plugin, try: data = Popen("env LC_ALL=C darcs changes", shell=True, - cwd=self.datastore, + cwd=self.vcs_root, stdout=PIPE).stdout.readlines() revision = data[0].strip('\n') except: msg = "Failed to read darcs repository" self.logger.error(msg) - self.logger.error('Ran command "darcs changes" from directory "%s"' - % self.datastore) + self.logger.error('Ran command "darcs changes" from directory %s' % + self.vcs_root) raise Bcfg2.Server.Plugin.PluginExecutionError(msg) return revision diff --git a/src/lib/Bcfg2/Server/Plugins/Fossil.py b/src/lib/Bcfg2/Server/Plugins/Fossil.py index 85d0f38f5..f6735df12 100644 --- a/src/lib/Bcfg2/Server/Plugins/Fossil.py +++ b/src/lib/Bcfg2/Server/Plugins/Fossil.py @@ -5,16 +5,14 @@ from subprocess import Popen, PIPE import Bcfg2.Server.Plugin -class Fossil(Bcfg2.Server.Plugin.Plugin, - Bcfg2.Server.Plugin.Version): +class Fossil(Bcfg2.Server.Plugin.Version): """ The Fossil plugin provides a revision interface for Bcfg2 repos using fossil. """ __author__ = 'bcfg-dev@mcs.anl.gov' __vcs_metadata_path__ = "_FOSSIL_" def __init__(self, core, datastore): - Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) - Bcfg2.Server.Plugin.Version.__init__(self, datastore) + Bcfg2.Server.Plugin.Version.__init__(self, core, datastore) self.logger.debug("Initialized Fossil plugin with fossil directory %s" % self.vcs_path) @@ -23,7 +21,7 @@ class Fossil(Bcfg2.Server.Plugin.Plugin, try: data = Popen("env LC_ALL=C fossil info", shell=True, - cwd=self.datastore, + cwd=self.vcs_root, stdout=PIPE).stdout.readlines() revline = [line.split(': ')[1].strip() for line in data if \ line.split(': ')[0].strip() == 'checkout'][-1] @@ -32,5 +30,5 @@ class Fossil(Bcfg2.Server.Plugin.Plugin, msg = "Failed to read fossil info" self.logger.error(msg) self.logger.error('Ran command "fossil info" from directory "%s"' % - self.datastore) + self.vcs_root) raise Bcfg2.Server.Plugin.PluginExecutionError(msg) diff --git a/src/lib/Bcfg2/Server/Plugins/Git.py b/src/lib/Bcfg2/Server/Plugins/Git.py index 177770a12..b7269af48 100644 --- a/src/lib/Bcfg2/Server/Plugins/Git.py +++ b/src/lib/Bcfg2/Server/Plugins/Git.py @@ -1,38 +1,153 @@ """ The Git plugin provides a revision interface for Bcfg2 repos using git. """ +import os +import sys +import Bcfg2.Server.Plugin + + +class GitAPIBase(object): + """ Base class for the various Git APIs (dulwich, GitPython, + subprocesses) """ + def __init__(self, path): + self.path = path + + def revision(self): + """ Get the current revision of the git repo as a string """ + raise NotImplementedError + + def pull(self): + """ Pull the latest version of the upstream git repo and + rebase against it. """ + raise NotImplementedError + + try: + from dulwich.client import get_transport_and_path from dulwich.repo import Repo + from dulwich.file import GitFile, ensure_dir_exists + + class GitAPI(GitAPIBase): + """ API for :class:`Git` using :mod:`dulwich` """ + def __init__(self, path): + GitAPIBase.__init__(self, path) + self.repo = Repo(self.path) + self.client, self.origin_path = get_transport_and_path( + self.repo.get_config().get(("remote", "origin"), + "url")) + + def revision(self): + return self.repo.head() + + def pull(self): + try: + remote_refs = self.client.fetch( + self.origin_path, self.repo, + determine_wants=self.repo.object_store.determine_wants_all) + except KeyError: + etype, err = sys.exc_info()[:2] + # try to work around bug + # https://bugs.launchpad.net/dulwich/+bug/1025886 + try: + # pylint: disable=W0212 + self.client._fetch_capabilities.remove('thin-pack') + # pylint: enable=W0212 + except KeyError: + raise etype(err) + remote_refs = self.client.fetch( + self.origin_path, self.repo, + determine_wants=self.repo.object_store.determine_wants_all) + + tree_id = self.repo[remote_refs['HEAD']].tree + # iterate over tree content, giving path and blob sha. + for entry in self.repo.object_store.iter_tree_contents(tree_id): + entry_in_path = entry.in_path(self.repo.path) + ensure_dir_exists(os.path.split(entry_in_path.path)[0]) + GitFile(entry_in_path.path, + 'wb').write(self.repo[entry.sha].data) + except ImportError: - # fallback to shell commands when dulwich unavailable - from subprocess import Popen, PIPE + try: + import git -import Bcfg2.Server.Plugin + class GitAPI(GitAPIBase): + """ API for :class:`Git` using :mod:`git` (GitPython) """ + def __init__(self, path): + GitAPIBase.__init__(self, path) + self.repo = git.Repo(path) + def revision(self): + return self.repo.head.commit.hexsha -class Git(Bcfg2.Server.Plugin.Plugin, - Bcfg2.Server.Plugin.Version): + def pull(self): + self.repo.git.pull("--rebase") + + except ImportError: + from subprocess import Popen, PIPE + + try: + Popen(["git"], stdout=PIPE, stderr=PIPE).wait() + + class GitAPI(GitAPIBase): + """ API for :class:`Git` using subprocess to run git + commands """ + def revision(self): + proc = Popen(["git", "--work-tree", self.path, + "rev-parse", "HEAD"], stdout=PIPE, + stderr=PIPE) + rv, err = proc.communicate() + if proc.wait(): + raise Exception("Git: Error getting revision from %s: " + "%s" % (self.path, err)) + return rv.strip() # pylint: disable=E1103 + + def pull(self): + proc = Popen(["git", "--work-tree", self.path, + "pull", "--rebase"], stdout=PIPE, + stderr=PIPE) + err = proc.communicate()[1].strip() + if proc.wait(): + raise Exception("Git: Error pulling: %s" % err) + + except OSError: + raise ImportError("Could not import dulwich or GitPython " + "libraries, and no 'git' command found in PATH") + + +class Git(Bcfg2.Server.Plugin.Version): """ The Git plugin provides a revision interface for Bcfg2 repos using git. """ __author__ = 'bcfg-dev@mcs.anl.gov' __vcs_metadata_path__ = ".git" + __rmi__ = Bcfg2.Server.Plugin.Version.__rmi__ + ['Update'] def __init__(self, core, datastore): - Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) - Bcfg2.Server.Plugin.Version.__init__(self, datastore) + Bcfg2.Server.Plugin.Version.__init__(self, core, datastore) + self.repo = GitAPI(self.vcs_root) self.logger.debug("Initialized git plugin with git directory %s" % self.vcs_path) def get_revision(self): """Read git revision information for the Bcfg2 repository.""" try: - return Repo(self.datastore).head() - except NameError: - return Popen("env LC_ALL=C git log -1 --pretty=format:%H", - shell=True, - cwd=self.datastore, - stdout=PIPE).stdout.readline() + return self.repo.revision() except: - msg = "Failed to read git repository" + err = sys.exc_info()[1] + msg = "Failed to read git repository: %s" % err + self.logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + + def Update(self): + """ Git.Update() => True|False + Update the working copy against the upstream repository + """ + try: + self.repo.pull() + self.logger.info("Git repo at %s updated to %s" % + (self.vcs_root, self.get_revision())) + return True + except: # pylint: disable=W0702 + err = sys.exc_info()[1] + msg = "Failed to pull from git repository: %s" % err self.logger.error(msg) raise Bcfg2.Server.Plugin.PluginExecutionError(msg) diff --git a/src/lib/Bcfg2/Server/Plugins/Hg.py b/src/lib/Bcfg2/Server/Plugins/Hg.py index b5dec2e3f..3fd3918bd 100644 --- a/src/lib/Bcfg2/Server/Plugins/Hg.py +++ b/src/lib/Bcfg2/Server/Plugins/Hg.py @@ -1,32 +1,31 @@ """ The Hg plugin provides a revision interface for Bcfg2 repos using mercurial. """ +import sys from mercurial import ui, hg import Bcfg2.Server.Plugin -class Hg(Bcfg2.Server.Plugin.Plugin, - Bcfg2.Server.Plugin.Version): +class Hg(Bcfg2.Server.Plugin.Version): """ The Hg plugin provides a revision interface for Bcfg2 repos using mercurial. """ - __author__ = 'bcfg-dev@mcs.anl.gov' __vcs_metadata_path__ = ".hg" def __init__(self, core, datastore): - Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) - Bcfg2.Server.Plugin.Version.__init__(self, datastore) + Bcfg2.Server.Plugin.Version.__init__(self, core, datastore) self.logger.debug("Initialized hg plugin with hg directory %s" % self.vcs_path) def get_revision(self): """Read hg revision information for the Bcfg2 repository.""" try: - repo_path = self.datastore + "/" + repo_path = self.vcs_root + "/" repo = hg.repository(ui.ui(), repo_path) tip = repo.changelog.tip() return repo.changelog.rev(tip) except: - msg = "Failed to read hg repository" + err = sys.exc_info()[1] + msg = "Failed to read hg repository: %s" % err self.logger.error(msg) raise Bcfg2.Server.Plugin.PluginExecutionError(msg) diff --git a/src/lib/Bcfg2/Server/Plugins/Svn.py b/src/lib/Bcfg2/Server/Plugins/Svn.py index 5bc9d6bbd..fda6b57b5 100644 --- a/src/lib/Bcfg2/Server/Plugins/Svn.py +++ b/src/lib/Bcfg2/Server/Plugins/Svn.py @@ -1,34 +1,125 @@ """ The Svn plugin provides a revision interface for Bcfg2 repos using -svn. """ +Subversion. If PySvn libraries are installed, then it exposes two +additional XML-RPC methods for committing data to the repository and +updating the repository. """ -import pipes -from subprocess import Popen, PIPE +import sys import Bcfg2.Server.Plugin +try: + import pysvn + HAS_SVN = True +except ImportError: + import pipes + from subprocess import Popen, PIPE + HAS_SVN = False -class Svn(Bcfg2.Server.Plugin.Plugin, - Bcfg2.Server.Plugin.Version): - """ The Svn plugin provides a revision interface for Bcfg2 repos - using svn. """ +class Svn(Bcfg2.Server.Plugin.Version): + """Svn is a version plugin for dealing with Bcfg2 repos.""" __author__ = 'bcfg-dev@mcs.anl.gov' - __vcs_metadata_path__ = ".svn" + if HAS_SVN: + __rmi__ = Bcfg2.Server.Plugin.Version.__rmi__ + ['Update', 'Commit'] + else: + __vcs_metadata_path__ = ".svn" def __init__(self, core, datastore): - Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) - Bcfg2.Server.Plugin.Version.__init__(self, datastore) - self.logger.debug("Initialized svn plugin with svn directory %s" % + Bcfg2.Server.Plugin.Version.__init__(self, core, datastore) + + self.revision = None + self.svn_root = None + if not HAS_SVN: + self.logger.debug("Svn: PySvn not found, using CLI interface to " + "SVN") + self.client = None + else: + self.client = pysvn.Client() + + self.logger.debug("Initialized svn plugin with SVN directory %s" % self.vcs_path) def get_revision(self): """Read svn revision information for the Bcfg2 repository.""" + msg = None + if HAS_SVN: + try: + info = self.client.info(self.vcs_root) + self.revision = info.revision + self.svn_root = info.url + return str(self.revision.number) + except pysvn.ClientError: # pylint: disable=E1101 + msg = "Svn: Failed to get revision: %s" % sys.exc_info()[1] + else: + try: + data = Popen(("env LC_ALL=C svn info %s" % + pipes.quote(self.vcs_root)), shell=True, + stdout=PIPE).communicate()[0].split('\n') + return [line.split(': ')[1] for line in data \ + if line[:9] == 'Revision:'][-1] + except IndexError: + msg = "Failed to read svn info" + self.logger.error('Ran command "svn info %s"' % self.vcs_root) + self.revision = None + self.logger.error(msg) + raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + + def Update(self): + '''Svn.Update() => True|False\nUpdate svn working copy\n''' try: - data = Popen(("env LC_ALL=C svn info %s" % - pipes.quote(self.datastore)), shell=True, - stdout=PIPE).communicate()[0].split('\n') - return [line.split(': ')[1] for line in data \ - if line[:9] == 'Revision:'][-1] - except IndexError: - msg = "Failed to read svn info" - self.logger.error(msg) - self.logger.error('Ran command "svn info %s"' % self.datastore) - raise Bcfg2.Server.Plugin.PluginExecutionError(msg) + old_revision = self.revision.number + self.revision = self.client.update(self.vcs_root, recurse=True)[0] + except pysvn.ClientError: # pylint: disable=E1101 + err = sys.exc_info()[1] + # try to be smart about the error we got back + details = None + if "callback_ssl_server_trust_prompt" in str(err): + details = "SVN server certificate is not trusted" + elif "callback_get_login" in str(err): + details = "SVN credentials not cached" + + if details is None: + self.logger.error("Svn: Failed to update server repository", + exc_info=1) + else: + self.logger.error("Svn: Failed to update server repository: " + "%s" % details) + return False + + if old_revision == self.revision.number: + self.logger.debug("repository is current") + else: + self.logger.info("Updated %s from revision %s to %s" % \ + (self.vcs_root, old_revision, self.revision.number)) + return True + + def Commit(self): + """Svn.Commit() => True|False\nCommit svn repository\n""" + # First try to update + if not self.Update(): + self.logger.error("Failed to update svn repository, refusing to " + "commit changes") + return False + + try: + self.revision = self.client.checkin([self.vcs_root], + 'Svn: autocommit', + recurse=True) + self.revision = self.client.update(self.vcs_root, recurse=True)[0] + self.logger.info("Svn: Commited changes. At %s" % + self.revision.number) + return True + except pysvn.ClientError: # pylint: disable=E1101 + err = sys.exc_info()[1] + # try to be smart about the error we got back + details = None + if "callback_ssl_server_trust_prompt" in str(err): + details = "SVN server certificate is not trusted" + elif "callback_get_login" in str(err): + details = "SVN credentials not cached" + + if details is None: + self.logger.error("Svn: Failed to commit changes", + exc_info=1) + else: + self.logger.error("Svn: Failed to commit changes: %s" % + details) + return False diff --git a/src/lib/Bcfg2/Server/Plugins/Svn2.py b/src/lib/Bcfg2/Server/Plugins/Svn2.py deleted file mode 100644 index 19093eb6a..000000000 --- a/src/lib/Bcfg2/Server/Plugins/Svn2.py +++ /dev/null @@ -1,116 +0,0 @@ -""" The Svn2 plugin provides a revision interface for Bcfg2 repos using -Subversion. It uses a pure python backend and exposes two additional -XML-RPC methods. """ - -import sys -import Bcfg2.Server.Plugin -try: - import pysvn - HAS_SVN = False -except ImportError: - HAS_SVN = True - - -class Svn2(Bcfg2.Server.Plugin.Plugin, - Bcfg2.Server.Plugin.Version): - """Svn is a version plugin for dealing with Bcfg2 repos.""" - __author__ = 'bcfg-dev@mcs.anl.gov' - - conflicts = ['Svn'] - __rmi__ = Bcfg2.Server.Plugin.Plugin.__rmi__ + ['Update', 'Commit'] - - def __init__(self, core, datastore): - Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) - Bcfg2.Server.Plugin.Version.__init__(self, datastore) - - if HAS_SVN: - msg = "Missing PySvn" - self.logger.error("Svn2: " + msg) - raise Bcfg2.Server.Plugin.PluginInitError(msg) - - self.client = pysvn.Client() - - self.svn_root = None - self.revision = None - - # Read revision from bcfg2 repo - revision = self.get_revision() - if not self.revision: - raise Bcfg2.Server.Plugin.PluginInitError("Failed to get revision") - - self.logger.debug("Initialized svn plugin with svn root %s at " - "revision %s" % (self.svn_root, revision)) - - def get_revision(self): - """Read svn revision information for the Bcfg2 repository.""" - try: - info = self.client.info(self.datastore) - self.revision = info.revision - self.svn_root = info.url - return str(self.revision.number) - except pysvn.ClientError: # pylint: disable=E1101 - self.logger.error("Svn2: Failed to get revision", exc_info=1) - self.revision = None - return str(-1) - - def Update(self): - '''Svn2.Update() => True|False\nUpdate svn working copy\n''' - try: - old_revision = self.revision.number - self.revision = self.client.update(self.datastore, recurse=True)[0] - except pysvn.ClientError: # pylint: disable=E1101 - err = sys.exc_info()[1] - # try to be smart about the error we got back - details = None - if "callback_ssl_server_trust_prompt" in str(err): - details = "SVN server certificate is not trusted" - elif "callback_get_login" in str(err): - details = "SVN credentials not cached" - - if details is None: - self.logger.error("Svn2: Failed to update server repository", - exc_info=1) - else: - self.logger.error("Svn2: Failed to update server repository: " - "%s" % details) - return False - - if old_revision == self.revision.number: - self.logger.debug("repository is current") - else: - self.logger.info("Updated %s from revision %s to %s" % \ - (self.datastore, old_revision, self.revision.number)) - return True - - def Commit(self): - """Svn2.Commit() => True|False\nCommit svn repository\n""" - # First try to update - if not self.Update(): - self.logger.error("Failed to update svn repository, refusing to " - "commit changes") - return False - - try: - self.revision = self.client.checkin([self.datastore], - 'Svn2: autocommit', - recurse=True) - self.revision = self.client.update(self.datastore, recurse=True)[0] - self.logger.info("Svn2: Commited changes. At %s" % - self.revision.number) - return True - except pysvn.ClientError: # pylint: disable=E1101 - err = sys.exc_info()[1] - # try to be smart about the error we got back - details = None - if "callback_ssl_server_trust_prompt" in str(err): - details = "SVN server certificate is not trusted" - elif "callback_get_login" in str(err): - details = "SVN credentials not cached" - - if details is None: - self.logger.error("Svn2: Failed to commit changes", - exc_info=1) - else: - self.logger.error("Svn2: Failed to commit changes: %s" % - details) - return False |