subversion 6.93 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 97 98 99 100 101
        bits = [
            self.svn_path,
            '--non-interactive',
            '--trust-server-cert',
            '--no-auth-cache',
        ]
102 103 104 105 106
        if self.username:
            bits.append("--username '%s'" % self.username)
        if self.password:
            bits.append("--password '%s'" % self.password)
        bits.append(args)
107
        rc, out, err = self.module.run_command(' '.join(bits), check_rc=True)
108 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
        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

142 143 144 145 146 147 148 149 150 151 152
    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

153 154 155 156 157

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

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

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

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

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

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

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