subversion 6.95 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
#!/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/>.

21 22 23 24 25
DOCUMENTATION = '''
---
module: subversion
short_description: Deploys a subversion repository.
description:
26
   - Deploy given repository URL / revision to dest. If dest exists, update to the specified revision, otherwise perform a checkout.
27
version_added: "0.7"
28 29 30 31
author: Dane Summers, njharman@gmail.com
notes:
   - Requres I(svn) to be installed on the client.
requirements: []
32 33 34 35 36
options:
  repo:
    description:
      - The subversion URL to the repository.
    required: true
37
    aliases: [ name, repository ]
38 39 40 41 42 43
    default: null
  dest:
    description:
      - Absolute path where the repository should be deployed.
    required: true
    default: null
44 45 46 47 48
  revision:
    description:
      - Specific revision to checkout.
    required: false
    default: HEAD
49
    aliases: [ version ]
50 51
  force:
    description:
52
      - If C(yes), modified files will be discarded. If C(no), module will fail if it encounters modified files.
53
    required: false
54
    default: "yes"
55
    choices: [ "yes", "no" ]
56 57 58 59 60 61 62 63 64 65
  username:
    description:
      - --username parameter passed to svn.
    required: false
    default: null
  password:
    description:
      - --password parameter passed to svn.
    required: false
    default: null
66 67 68 69 70 71
  executable:
    required: false
    default: null
    version_added: "1.4"
    description:
      - Path to svn executable to use. If not supplied,
72
        the normal mechanism for resolving binary paths will be used.
73 74 75 76 77
'''

EXAMPLES = '''
# Checkout subversion repository to specified folder.
- subversion: repo=svn+ssh://an.example.org/path/to/repo dest=/src/checkout
78
'''
79 80

import re
81
import tempfile
82

83 84

class Subversion(object):
85 86
    def __init__(
            self, module, dest, repo, revision, username, password, svn_path):
87
        self.module = module
88 89 90 91 92
        self.dest = dest
        self.repo = repo
        self.revision = revision
        self.username = username
        self.password = password
93
        self.svn_path = svn_path
94 95

    def _exec(self, args):
96
        bits = [
97
            'LANG=C',
98 99 100 101 102
            self.svn_path,
            '--non-interactive',
            '--trust-server-cert',
            '--no-auth-cache',
        ]
103 104 105 106 107
        if self.username:
            bits.append("--username '%s'" % self.username)
        if self.password:
            bits.append("--password '%s'" % self.password)
        bits.append(args)
108
        rc, out, err = self.module.run_command(' '.join(bits), check_rc=True)
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
        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

143 144 145 146 147 148 149 150 151 152 153
    def needs_update(self):
        curr, url = self.get_revision()
        out2 = '\n'.join(self._exec("info -r HEAD '%s'" % 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

154 155 156 157 158

# ===========================================

def main():
    module = AnsibleModule(
159
        argument_spec=dict(
160
            dest=dict(required=True),
161
            repo=dict(required=True, aliases=['name', 'repository']),
162
            revision=dict(default='HEAD', aliases=['rev', 'version']),
163
            force=dict(default='yes', type='bool'),
164 165
            username=dict(required=False),
            password=dict(required=False),
166
            executable=dict(default=None),
167 168
        ),
        supports_check_mode=True
169 170
    )

171 172
    dest = os.path.expanduser(module.params['dest'])
    repo = module.params['repo']
173
    revision = module.params['revision']
174
    force = module.params['force']
175 176
    username = module.params['username']
    password = module.params['password']
177
    svn_path = module.params['executable'] or module.get_bin_path('svn', True)
178

179
    svn = Subversion(module, dest, repo, revision, username, password, svn_path)
180 181 182 183

    if not os.path.exists(dest):
        before = None
        local_mods = False
184 185
        if module.check_mode:
            module.exit_json(changed=True)
186 187 188 189 190
        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.
191
        if module.check_mode:
192 193
            check, before, after = svn.needs_update()
            module.exit_json(changed=check, before=before, after=after)
194 195 196 197 198 199 200 201 202
        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()
203
    else:
204
        module.fail_json(msg="ERROR: %s folder already exists, but its not a subversion repository." % (dest, ))
205

206 207
    after = svn.get_revision()
    changed = before != after or local_mods
208
    module.exit_json(changed=changed, before=before, after=after)
209

210 211
# import module snippets
from ansible.module_utils.basic import *
212
main()