ansible/library/subversion
Norman J. Harman Jr 2e6999dab9 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'.
2012-12-10 20:34:26 -06:00

177 lines
5.9 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible 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, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible 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 Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: subversion
short_description: Deploys a subversion repository.
description:
- 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:
- The subversion URL to the repository.
required: true
default: null
dest:
description:
- 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), 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: Checkout subversion repository to specified folder.
'''
import re
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 _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 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 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 main():
module = AnsibleModule(
argument_spec=dict(
dest=dict(required=True),
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']
revision = module.params['revision']
force = module.boolean(module.params['force'])
username = module.params['username']
password = module.params['password']
svn = Subversion(module.fail_json, dest, repo, revision, username, password)
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:
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
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()