From f2fe4d71d31c9e0f15142f7cfc127f2889f24d4b Mon Sep 17 00:00:00 2001 From: "Norman J. Harman Jr" Date: Mon, 10 Dec 2012 20:34:26 -0600 Subject: [PATCH] Subverion module improvements - Added username, password arguments. - Documented existing revision argument. - Corrected documentation/docstrings; removed git references, use svn nomenclature, etc. - Refactored duplicate code, redundant shell calls, filter abuse, inconsistent formating, etc. - Shell quoting so it doesn't break for one guy who has spaces in pathnames. - svn called with '--non-interactive' and '--no-auth-cache'. --- subversion | 211 ++++++++++++++++++++++++++--------------------------- 1 file changed, 105 insertions(+), 106 deletions(-) diff --git a/subversion b/subversion index 3ba3a74e934..88699ab0e7b 100644 --- a/subversion +++ b/subversion @@ -23,8 +23,12 @@ DOCUMENTATION = ''' module: subversion short_description: Deploys a subversion repository. description: - - This module is really simple, it checks out from the given branch of a repo or at a particular tag. + - Deploy given repository URL / revision to dest. version_added: "0.7" +author: Dane Summers, njharman@gmail.com +notes: + - Requres I(svn) to be installed on the client. +requirements: [] options: repo: description: @@ -36,141 +40,136 @@ options: - Absolute path where the repository should be deployed. required: true default: null + revision: + description: + - Specific revision to checkout. + required: false + default: HEAD force: description: - - If C(yes), any modified files in the working repository will be discarded. If C(no), this module will fail if it encounters modified files. + - If C(yes), modified files will be discarded. If C(no), module will fail if it encounters modified files. required: false default: yes choices: [ "yes", "no" ] + username: + description: + - --username parameter passed to svn. + required: false + default: null + password: + description: + - --password parameter passed to svn. + required: false + default: null examples: - code: "subversion: repo=svn+ssh://an.example.org/path/to/repo dest=/src/checkout" - description: Export subversion repository in a specified folder -notes: - - Requires I(subversion) and I(grep) on the client. -requirements: [ ] -author: Dane Summers + description: Checkout subversion repository to specified folder. ''' import re -def get_version(dest): - ''' samples the version of the git repo ''' - os.chdir(dest) - cmd = "svn info" - revision = filter(lambda l: re.search('Revision',l) != None,os.popen(cmd).read().splitlines()) - url = filter(lambda l: re.search('^URL',l) != None,os.popen(cmd).read().splitlines()) - return [revision[0],url[0]] -def checkout(repo, dest): - ''' makes a new svn repo if it does not already exist ''' - cmd = "svn co %s %s" % (repo, dest) - cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (out, err) = cmd.communicate() - rc = cmd.returncode - return (rc, out, err) +class Subversion(object): + def __init__(self, fail_callback, dest, repo, revision, username, password): + self.fail_callback = fail_callback + self.dest = dest + self.repo = repo + self.revision = revision + self.username = username + self.password = password -def switch(repo, dest): - ''' makes a new svn repo if it does not already exist ''' - cmd = "svn sw %s %s" % (repo, dest) - cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (out, err) = cmd.communicate() - rc = cmd.returncode - return (rc, out, err) + def _exec(self, args): + bits = ["svn --non-interactive --no-auth-cache", ] + if self.username: + bits.append("--username '%s'" % self.username) + if self.password: + bits.append("--password '%s'" % self.password) + bits.append(args) + cmd = subprocess.Popen(' '.join(bits), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = cmd.communicate() + rc = cmd.returncode + if rc != 0: + self.fail_callback(msg=err) + return out.splitlines() -def has_local_mods(dest): - os.chdir(dest) - cmd = "svn status" - lines = os.popen(cmd).read().splitlines() - filtered = filter(lambda c: re.search('^\\?.*$',c) == None,lines) - return len(filtered) > 0 + def checkout(self): + '''Creates new svn working directory if it does not already exist.''' + self._exec("checkout -r %s '%s' '%s'" % (self.revision, self.repo, self.dest)) -def reset(dest,force): - ''' - Reset the repo: - force: if true, then remove any local modifications. Else, fail if there are local modifications - ''' - if has_local_mods(dest): - if force: - cmd = "svn revert -R ." - cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (out, err) = cmd.communicate() - rc = cmd.returncode - return (rc, out, err) - else: - return (-1,"ERROR: modified files exist in the repository.","") - return (0,"","") + def switch(self): + '''Change working directory's repo.''' + # switch to ensure we are pointing at correct repo. + self._exec("switch '%s' '%s'" % (self.repo, self.dest)) + + def update(self): + '''Update existing svn working directory.''' + self._exec("update -r %s '%s'" % (self.revision, self.dest)) + + def revert(self): + '''Revert svn working directory.''' + self._exec("revert -R '%s'" % self.dest) + + def get_revision(self): + '''Revision and URL of subversion working directory.''' + text = '\n'.join(self._exec("info '%s'" % self.dest)) + rev = re.search(r'^Revision:.*$', text, re.MULTILINE).group(0) + url = re.search(r'^URL:.*$', text, re.MULTILINE).group(0) + return rev, url + + def has_local_mods(self): + '''True if revisioned files have been added or modified. Unrevisioned files are ignored.''' + lines = self._exec("status '%s'" % self.dest) + # Match only revisioned files, i.e. ignore status '?'. + regex = re.compile(r'^[^?]') + # Has local mods if more than 0 modifed revisioned files. + return len(filter(regex.match, lines)) > 0 -def update(module, dest, version): - ''' update an existing svn repo ''' - os.chdir(dest) - cmd = '' - if version != 'HEAD': - cmd = "svn up -r %s" % version - else: - cmd = "svn up" - cmd = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (out, err) = cmd.communicate() - rc = cmd.returncode - return (rc, out, err) # =========================================== def main(): module = AnsibleModule( - argument_spec = dict( + argument_spec=dict( dest=dict(required=True), - repo=dict(required=True, aliases=['name']), - revision=dict(default='HEAD'), - force=dict(default='yes', choices=['yes', 'no'], aliases=['force']) + repo=dict(required=True, aliases=['name', 'repository']), + revision=dict(default='HEAD', aliases=['rev']), + force=dict(default='yes', choices=['yes', 'no']), + username=dict(required=False), + password=dict(required=False), ) ) - dest = os.path.expanduser(module.params['dest']) - repo = module.params['repo'] + dest = os.path.expanduser(module.params['dest']) + repo = module.params['repo'] revision = module.params['revision'] - force = module.boolean(module.params['force']) + force = module.boolean(module.params['force']) + username = module.params['username'] + password = module.params['password'] - rc, out, err, status = (0, None, None, None) + svn = Subversion(module.fail_json, dest, repo, revision, username, password) - # if there is no .svn folder, do a checkout - # else update. - before = None - local_mods = False - if not os.path.exists("%s/.svn" % (dest)): - if os.path.exists(dest): - module.fail_json(msg="%s folder already exists, but its not a subversion repository." % (dest)) - else: - (rc, out, err) = checkout(repo, dest) - if rc != 0: - module.fail_json(msg=err) + if not os.path.exists(dest): + before = None + local_mods = False + svn.checkout() + elif os.path.exists("%s/.svn" % (dest, )): + # Order matters. Need to get local mods before switch to avoid false + # positives. Need to switch before revert to ensure we are reverting to + # correct repo. + before = svn.get_revision() + local_mods = svn.has_local_mods() + svn.switch() + if local_mods: + if force: + svn.revert() + else: + module.fail_json(msg="ERROR: modified files exist in the repository.") + svn.update() else: - local_mods = has_local_mods(dest) - # else do an update - before = get_version(dest) - (rc, out, err) = reset(dest,force) - if rc != 0: - module.fail_json(msg=err) - (rc, out, err) = switch(repo, dest) - if rc != 0: - module.fail_json(msg=err) - - # handle errors from switch or pull - if err.find('ERROR') != -1: - module.fail_json(msg=err) - - # switch to version specified regardless of whether - # we cloned or pulled - (rc, out, err) = update(module, dest, revision) - if rc != 0: - module.fail_json(msg=err) - - # determine if we changed anything - after = get_version(dest) - changed = False - - if before != after or local_mods: - changed = True + module.fail_json(msg="ERROR: %s folder already exists, but its not a subversion repository." % (dest, )) + after = svn.get_revision() + changed = before != after or local_mods module.exit_json(changed=changed, before=before, after=after) # include magic from lib/ansible/module_common.py