community.general/lib/ansible/modules/source_control/subversion.py
Matthew Jones 107a473dd8 Add separate checkout and update parameters (#5306)
* Add separate checkout and update parameters

This brings the svn module in line with the git module for controlling
individual update and checkout functionality based on whether the
directory exists or not.

It also allows specifying `no` for both to pull the remote revision
without performing a checkout

* Update version-added for new parameters
2016-12-08 11:25:22 -05:00

299 lines
10 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. If dest exists, update to the specified revision, otherwise perform a checkout.
version_added: "0.7"
author: "Dane Summers (@dsummersl) <njharman@gmail.com>"
notes:
- Requires I(svn) to be installed on the client.
- This module does not handle externals
requirements: []
options:
repo:
description:
- The subversion URL to the repository.
required: true
aliases: [ name, repository ]
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
aliases: [ version ]
force:
description:
- If C(yes), modified files will be discarded. If C(no), module will fail if it encounters modified files.
Prior to 1.9 the default was `yes`.
required: false
default: "no"
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
executable:
required: false
default: null
version_added: "1.4"
description:
- Path to svn executable to use. If not supplied,
the normal mechanism for resolving binary paths will be used.
checkout:
required: false
default: "yes"
choices: [ "yes", "no" ]
version_added: "2.3"
description:
- If no, do not check out the repository if it does not exist locally
update:
required: false
default: "yes"
choices: [ "yes", "no" ]
version_added: "2.3"
description:
- If no, do not retrieve new revisions from the origin repository
export:
required: false
default: "no"
choices: [ "yes", "no" ]
version_added: "1.6"
description:
- If C(yes), do export instead of checkout/update.
switch:
required: false
default: "yes"
choices: [ "yes", "no" ]
version_added: "2.0"
description:
- If C(no), do not call svn switch before update.
'''
EXAMPLES = '''
# Checkout subversion repository to specified folder.
- subversion: repo=svn+ssh://an.example.org/path/to/repo dest=/src/checkout
# Export subversion directory to folder
- subversion: repo=svn+ssh://an.example.org/path/to/repo dest=/src/export export=True
# Example just get information about the repository whether or not it has
# already been cloned locally.
- subversion: repo=svn+ssh://an.example.org/path/to/repo dest=/srv/checkout checkout=no update=no
'''
import re
import tempfile
class Subversion(object):
def __init__(
self, module, dest, repo, revision, username, password, svn_path):
self.module = module
self.dest = dest
self.repo = repo
self.revision = revision
self.username = username
self.password = password
self.svn_path = svn_path
def _exec(self, args, check_rc=True):
'''Execute a subversion command, and return output. If check_rc is False, returns the return code instead of the output.'''
bits = [
self.svn_path,
'--non-interactive',
'--trust-server-cert',
'--no-auth-cache',
]
if self.username:
bits.extend(["--username", self.username])
if self.password:
bits.extend(["--password", self.password])
bits.extend(args)
rc, out, err = self.module.run_command(bits, check_rc)
if check_rc:
return out.splitlines()
else:
return rc
def is_svn_repo(self):
'''Checks if path is a SVN Repo.'''
rc = self._exec(["info", self.dest], check_rc=False)
return rc == 0
def checkout(self):
'''Creates new svn working directory if it does not already exist.'''
self._exec(["checkout", "-r", self.revision, self.repo, self.dest])
def export(self, force=False):
'''Export svn repo to directory'''
cmd = ["export"]
if force:
cmd.append("--force")
cmd.extend(["-r", self.revision, self.repo, self.dest])
self._exec(cmd)
def switch(self):
'''Change working directory's repo.'''
# switch to ensure we are pointing at correct repo.
self._exec(["switch", self.repo, self.dest])
def update(self):
'''Update existing svn working directory.'''
self._exec(["update", "-r", self.revision, self.dest])
def revert(self):
'''Revert svn working directory.'''
self._exec(["revert", "-R", self.dest])
def get_revision(self):
'''Revision and URL of subversion working directory.'''
text = '\n'.join(self._exec(["info", 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 get_remote_revision(self):
'''Revision and URL of subversion working directory.'''
text = '\n'.join(self._exec(["info", self.repo]))
rev = re.search(r'^Revision:.*$', text, re.MULTILINE).group(0)
return rev
def has_local_mods(self):
'''True if revisioned files have been added or modified. Unrevisioned files are ignored.'''
lines = self._exec(["status", "--quiet", "--ignore-externals", self.dest])
# The --quiet option will return only modified files.
# Match only revisioned files, i.e. ignore status '?'.
regex = re.compile(r'^[^?X]')
# Has local mods if more than 0 modified revisioned files.
return len(list(filter(regex.match, lines))) > 0
def needs_update(self):
curr, url = self.get_revision()
out2 = '\n'.join(self._exec(["info", "-r", "HEAD", self.dest]))
head = re.search(r'^Revision:.*$', out2, re.MULTILINE).group(0)
rev1 = int(curr.split(':')[1].strip())
rev2 = int(head.split(':')[1].strip())
change = False
if rev1 < rev2:
change = True
return change, curr, head
# ===========================================
def main():
module = AnsibleModule(
argument_spec=dict(
dest=dict(type='path'),
repo=dict(required=True, aliases=['name', 'repository']),
revision=dict(default='HEAD', aliases=['rev', 'version']),
force=dict(default='no', type='bool'),
username=dict(required=False),
password=dict(required=False, no_log=True),
executable=dict(default=None, type='path'),
export=dict(default=False, required=False, type='bool'),
checkout=dict(default=True, required=False, type='bool'),
update=dict(default=True, required=False, type='bool'),
switch=dict(default=True, required=False, type='bool'),
),
supports_check_mode=True
)
dest = module.params['dest']
repo = module.params['repo']
revision = module.params['revision']
force = module.params['force']
username = module.params['username']
password = module.params['password']
svn_path = module.params['executable'] or module.get_bin_path('svn', True)
export = module.params['export']
switch = module.params['switch']
checkout = module.params['checkout']
update = module.params['update']
# We screenscrape a huge amount of svn commands so use C locale anytime we
# call run_command()
module.run_command_environ_update = dict(LANG='C', LC_MESSAGES='C')
if not dest and (checkout or update or export):
module.fail_json(msg="the destination directory must be specified unless checkout=no, update=no, and export=no")
svn = Subversion(module, dest, repo, revision, username, password, svn_path)
if not export and not update and not checkout:
module.exit_json(changed=False, after=svn.get_remote_revision())
if export or not os.path.exists(dest):
before = None
local_mods = False
if module.check_mode:
module.exit_json(changed=True)
elif not export and not checkout:
module.exit_json(changed=False)
if not export and checkout:
svn.checkout()
else:
svn.export(force=force)
elif svn.is_svn_repo():
# 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.
if module.check_mode or not update:
check, before, after = svn.needs_update()
module.exit_json(changed=check, before=before, after=after)
before = svn.get_revision()
local_mods = svn.has_local_mods()
if switch:
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, ))
if export:
module.exit_json(changed=True)
else:
after = svn.get_revision()
changed = before != after or local_mods
module.exit_json(changed=changed, before=before, after=after)
# import module snippets
from ansible.module_utils.basic import *
main()