1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2013, André Paramés <git@andreparames.com> 5# Based on the Git module by Michael DeHaan <michael.dehaan@gmail.com> 6# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 7 8from __future__ import absolute_import, division, print_function 9__metaclass__ = type 10 11ANSIBLE_METADATA = {'metadata_version': '1.1', 12 'status': ['preview'], 13 'supported_by': 'community'} 14 15DOCUMENTATION = u''' 16--- 17module: bzr 18author: 19- André Paramés (@andreparames) 20version_added: "1.1" 21short_description: Deploy software (or files) from bzr branches 22description: 23 - Manage I(bzr) branches to deploy files or software. 24options: 25 name: 26 description: 27 - SSH or HTTP protocol address of the parent branch. 28 aliases: [ parent ] 29 required: yes 30 dest: 31 description: 32 - Absolute path of where the branch should be cloned to. 33 required: yes 34 version: 35 description: 36 - What version of the branch to clone. This can be the 37 bzr revno or revid. 38 default: head 39 force: 40 description: 41 - If C(yes), any modified files in the working 42 tree will be discarded. Before 1.9 the default 43 value was C(yes). 44 type: bool 45 default: 'no' 46 executable: 47 description: 48 - Path to bzr executable to use. If not supplied, 49 the normal mechanism for resolving binary paths will be used. 50 version_added: '1.4' 51''' 52 53EXAMPLES = ''' 54# Example bzr checkout from Ansible Playbooks 55- bzr: 56 name: bzr+ssh://foosball.example.org/path/to/branch 57 dest: /srv/checkout 58 version: 22 59''' 60 61import os 62import re 63 64from ansible.module_utils.basic import AnsibleModule 65 66 67class Bzr(object): 68 def __init__(self, module, parent, dest, version, bzr_path): 69 self.module = module 70 self.parent = parent 71 self.dest = dest 72 self.version = version 73 self.bzr_path = bzr_path 74 75 def _command(self, args_list, cwd=None, **kwargs): 76 (rc, out, err) = self.module.run_command([self.bzr_path] + args_list, cwd=cwd, **kwargs) 77 return (rc, out, err) 78 79 def get_version(self): 80 '''samples the version of the bzr branch''' 81 82 cmd = "%s revno" % self.bzr_path 83 rc, stdout, stderr = self.module.run_command(cmd, cwd=self.dest) 84 revno = stdout.strip() 85 return revno 86 87 def clone(self): 88 '''makes a new bzr branch if it does not already exist''' 89 dest_dirname = os.path.dirname(self.dest) 90 try: 91 os.makedirs(dest_dirname) 92 except Exception: 93 pass 94 if self.version.lower() != 'head': 95 args_list = ["branch", "-r", self.version, self.parent, self.dest] 96 else: 97 args_list = ["branch", self.parent, self.dest] 98 return self._command(args_list, check_rc=True, cwd=dest_dirname) 99 100 def has_local_mods(self): 101 102 cmd = "%s status -S" % self.bzr_path 103 rc, stdout, stderr = self.module.run_command(cmd, cwd=self.dest) 104 lines = stdout.splitlines() 105 106 lines = filter(lambda c: not re.search('^\\?\\?.*$', c), lines) 107 return len(lines) > 0 108 109 def reset(self, force): 110 ''' 111 Resets the index and working tree to head. 112 Discards any changes to tracked files in the working 113 tree since that commit. 114 ''' 115 if not force and self.has_local_mods(): 116 self.module.fail_json(msg="Local modifications exist in branch (force=no).") 117 return self._command(["revert"], check_rc=True, cwd=self.dest) 118 119 def fetch(self): 120 '''updates branch from remote sources''' 121 if self.version.lower() != 'head': 122 (rc, out, err) = self._command(["pull", "-r", self.version], cwd=self.dest) 123 else: 124 (rc, out, err) = self._command(["pull"], cwd=self.dest) 125 if rc != 0: 126 self.module.fail_json(msg="Failed to pull") 127 return (rc, out, err) 128 129 def switch_version(self): 130 '''once pulled, switch to a particular revno or revid''' 131 if self.version.lower() != 'head': 132 args_list = ["revert", "-r", self.version] 133 else: 134 args_list = ["revert"] 135 return self._command(args_list, check_rc=True, cwd=self.dest) 136 137 138# =========================================== 139 140def main(): 141 module = AnsibleModule( 142 argument_spec=dict( 143 dest=dict(type='path', required=True), 144 name=dict(type='str', required=True, aliases=['parent']), 145 version=dict(type='str', default='head'), 146 force=dict(type='bool', default='no'), 147 executable=dict(type='str'), 148 ) 149 ) 150 151 dest = module.params['dest'] 152 parent = module.params['name'] 153 version = module.params['version'] 154 force = module.params['force'] 155 bzr_path = module.params['executable'] or module.get_bin_path('bzr', True) 156 157 bzrconfig = os.path.join(dest, '.bzr', 'branch', 'branch.conf') 158 159 rc, out, err = (0, None, None) 160 161 bzr = Bzr(module, parent, dest, version, bzr_path) 162 163 # if there is no bzr configuration, do a branch operation 164 # else pull and switch the version 165 before = None 166 local_mods = False 167 if not os.path.exists(bzrconfig): 168 (rc, out, err) = bzr.clone() 169 170 else: 171 # else do a pull 172 local_mods = bzr.has_local_mods() 173 before = bzr.get_version() 174 (rc, out, err) = bzr.reset(force) 175 if rc != 0: 176 module.fail_json(msg=err) 177 (rc, out, err) = bzr.fetch() 178 if rc != 0: 179 module.fail_json(msg=err) 180 181 # switch to version specified regardless of whether 182 # we cloned or pulled 183 (rc, out, err) = bzr.switch_version() 184 185 # determine if we changed anything 186 after = bzr.get_version() 187 changed = False 188 189 if before != after or local_mods: 190 changed = True 191 192 module.exit_json(changed=changed, before=before, after=after) 193 194 195if __name__ == '__main__': 196 main() 197