mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-05-07 22:02:50 +00:00
Allow modules to be categorized, and also sort them when generating the documentation.
This commit is contained in:
174
library/source_control/bzr
Normal file
174
library/source_control/bzr
Normal file
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, André Paramés <git@andreparames.com>
|
||||
# Based on the Git module by 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 = u'''
|
||||
---
|
||||
module: bzr
|
||||
author: André Paramés
|
||||
version_added: "1.1"
|
||||
short_description: Deploy software (or files) from bzr branches
|
||||
description:
|
||||
- Manage I(bzr) branches to deploy files or software.
|
||||
options:
|
||||
name:
|
||||
required: true
|
||||
aliases: [ 'parent' ]
|
||||
description:
|
||||
- SSH or HTTP protocol address of the parent branch.
|
||||
dest:
|
||||
required: true
|
||||
description:
|
||||
- Absolute path of where the branch should be cloned to.
|
||||
version:
|
||||
required: false
|
||||
default: "head"
|
||||
description:
|
||||
- What version of the branch to clone. This can be the
|
||||
bzr revno or revid.
|
||||
force:
|
||||
required: false
|
||||
default: "yes"
|
||||
choices: [ yes, no ]
|
||||
description:
|
||||
- If C(yes), any modified files in the working
|
||||
tree will be discarded.
|
||||
examples:
|
||||
- code: "bzr name=bzr+ssh://foosball.example.org/path/to/branch dest=/srv/checkout version=22"
|
||||
description: Example bzr checkout from Ansible Playbooks
|
||||
'''
|
||||
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
def get_version(dest):
|
||||
''' samples the version of the bzr branch'''
|
||||
os.chdir(dest)
|
||||
cmd = "bzr revno"
|
||||
revno = os.popen(cmd).read().strip()
|
||||
return revno
|
||||
|
||||
def clone(module, parent, dest, version):
|
||||
''' makes a new bzr branch if it does not already exist '''
|
||||
dest_dirname = os.path.dirname(dest)
|
||||
try:
|
||||
os.makedirs(dest_dirname)
|
||||
except:
|
||||
pass
|
||||
os.chdir(dest_dirname)
|
||||
if version.lower() != 'head':
|
||||
cmd = "bzr branch -r %s %s %s" % (version, parent, dest)
|
||||
else:
|
||||
cmd = "bzr branch %s %s" % (parent, dest)
|
||||
return module.run_command(cmd, check_rc=True)
|
||||
|
||||
def has_local_mods(dest):
|
||||
os.chdir(dest)
|
||||
cmd = "bzr status -S"
|
||||
lines = os.popen(cmd).read().splitlines()
|
||||
lines = filter(lambda c: not re.search('^\\?\\?.*$', c), lines)
|
||||
return len(lines) > 0
|
||||
|
||||
def reset(module,dest,force):
|
||||
'''
|
||||
Resets the index and working tree to head.
|
||||
Discards any changes to tracked files in the working
|
||||
tree since that commit.
|
||||
'''
|
||||
os.chdir(dest)
|
||||
if not force and has_local_mods(dest):
|
||||
module.fail_json(msg="Local modifications exist in branch (force=no).")
|
||||
return module.run_command("bzr revert", check_rc=True)
|
||||
|
||||
def fetch(module, dest, version):
|
||||
''' updates branch from remote sources '''
|
||||
os.chdir(dest)
|
||||
if version.lower() != 'head':
|
||||
(rc, out, err) = module.run_command("bzr pull -r %s" % version)
|
||||
else:
|
||||
(rc, out, err) = module.run_command("bzr pull")
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed to pull")
|
||||
return (rc, out, err)
|
||||
|
||||
def switch_version(module, dest, version):
|
||||
''' once pulled, switch to a particular revno or revid'''
|
||||
os.chdir(dest)
|
||||
cmd = ''
|
||||
if version.lower() != 'head':
|
||||
cmd = "bzr revert -r %s" % version
|
||||
else:
|
||||
cmd = "bzr revert"
|
||||
return module.run_command(cmd, check_rc=True)
|
||||
|
||||
# ===========================================
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
dest=dict(required=True),
|
||||
name=dict(required=True, aliases=['parent']),
|
||||
version=dict(default='head'),
|
||||
force=dict(default='yes', type='bool')
|
||||
)
|
||||
)
|
||||
|
||||
dest = os.path.abspath(os.path.expanduser(module.params['dest']))
|
||||
parent = module.params['name']
|
||||
version = module.params['version']
|
||||
force = module.params['force']
|
||||
|
||||
bzrconfig = os.path.join(dest, '.bzr', 'branch', 'branch.conf')
|
||||
|
||||
rc, out, err, status = (0, None, None, None)
|
||||
|
||||
# if there is no bzr configuration, do a branch operation
|
||||
# else pull and switch the version
|
||||
before = None
|
||||
local_mods = False
|
||||
if not os.path.exists(bzrconfig):
|
||||
(rc, out, err) = clone(module, parent, dest, version)
|
||||
else:
|
||||
# else do a pull
|
||||
local_mods = has_local_mods(dest)
|
||||
before = get_version(dest)
|
||||
(rc, out, err) = reset(module, dest, force)
|
||||
if rc != 0:
|
||||
module.fail_json(msg=err)
|
||||
(rc, out, err) = fetch(module, dest, version)
|
||||
if rc != 0:
|
||||
module.fail_json(msg=err)
|
||||
|
||||
# switch to version specified regardless of whether
|
||||
# we cloned or pulled
|
||||
(rc, out, err) = switch_version(module, dest, version)
|
||||
|
||||
# determine if we changed anything
|
||||
after = get_version(dest)
|
||||
changed = False
|
||||
|
||||
if before != after or local_mods:
|
||||
changed = True
|
||||
|
||||
module.exit_json(changed=changed, before=before, after=after)
|
||||
|
||||
# include magic from lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
main()
|
||||
338
library/source_control/git
Normal file
338
library/source_control/git
Normal file
@@ -0,0 +1,338 @@
|
||||
#!/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: git
|
||||
author: Michael DeHaan
|
||||
version_added: "0.0.1"
|
||||
short_description: Deploy software (or files) from git checkouts
|
||||
description:
|
||||
- Manage I(git) checkouts of repositories to deploy files or software.
|
||||
options:
|
||||
repo:
|
||||
required: true
|
||||
aliases: [ name ]
|
||||
description:
|
||||
- git, SSH, or HTTP protocol address of the git repository.
|
||||
dest:
|
||||
required: true
|
||||
description:
|
||||
- Absolute path of where the repository should be checked out to.
|
||||
version:
|
||||
required: false
|
||||
default: "HEAD"
|
||||
description:
|
||||
- What version of the repository to check out. This can be the
|
||||
git I(SHA), the literal string C(HEAD), a branch name, or a tag name.
|
||||
remote:
|
||||
required: false
|
||||
default: "origin"
|
||||
description:
|
||||
- Name of the remote.
|
||||
force:
|
||||
required: false
|
||||
default: "yes"
|
||||
choices: [ "yes", "no" ]
|
||||
version_added: "0.7"
|
||||
description:
|
||||
- If C(yes), any modified files in the working
|
||||
repository will be discarded. Prior to 0.7, this was always
|
||||
'yes' and could not be disabled.
|
||||
depth:
|
||||
required: false
|
||||
default: null
|
||||
version_added: "1.2"
|
||||
description:
|
||||
- Create a shallow clone with a history truncated to the specified
|
||||
number or revisions. The minimum possible value is C(1), otherwise
|
||||
ignored.
|
||||
examples:
|
||||
- code: "git: repo=git://foosball.example.org/path/to/repo.git dest=/srv/checkout version=release-0.22"
|
||||
description: Example git checkout from Ansible Playbooks
|
||||
- code: "git: repo=ssh://git@github.com/mylogin/hello.git dest=/home/mylogin/hello"
|
||||
description: Example read-write git checkout from github
|
||||
'''
|
||||
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
def get_version(dest):
|
||||
''' samples the version of the git repo '''
|
||||
os.chdir(dest)
|
||||
cmd = "git show --abbrev-commit"
|
||||
sha = os.popen(cmd).read().split("\n")
|
||||
sha = sha[0].split()[1]
|
||||
return sha
|
||||
|
||||
def clone(module, repo, dest, remote, depth):
|
||||
''' makes a new git repo if it does not already exist '''
|
||||
dest_dirname = os.path.dirname(dest)
|
||||
try:
|
||||
os.makedirs(dest_dirname)
|
||||
except:
|
||||
pass
|
||||
os.chdir(dest_dirname)
|
||||
cmd = [ module.get_bin_path('git', True), 'clone', '-o', remote ]
|
||||
if depth:
|
||||
cmd.extend([ '--depth', str(depth) ])
|
||||
cmd.extend([ repo, dest ])
|
||||
return module.run_command(cmd, check_rc=True)
|
||||
|
||||
def has_local_mods(dest):
|
||||
os.chdir(dest)
|
||||
cmd = "git status -s"
|
||||
lines = os.popen(cmd).read().splitlines()
|
||||
lines = filter(lambda c: not re.search('^\\?\\?.*$', c), lines)
|
||||
return len(lines) > 0
|
||||
|
||||
def reset(module, dest, force):
|
||||
'''
|
||||
Resets the index and working tree to HEAD.
|
||||
Discards any changes to tracked files in working
|
||||
tree since that commit.
|
||||
'''
|
||||
os.chdir(dest)
|
||||
if not force and has_local_mods(dest):
|
||||
module.fail_json(msg="Local modifications exist in repository (force=no).")
|
||||
return module.run_command("git reset --hard HEAD", check_rc=True)
|
||||
|
||||
def get_remote_head(module, dest, version, remote):
|
||||
cmd = ''
|
||||
os.chdir(dest)
|
||||
if version == 'HEAD':
|
||||
version = get_head_branch(module, dest, remote)
|
||||
if is_remote_branch(module, dest, remote, version):
|
||||
cmd = 'git ls-remote %s -h refs/heads/%s' % (remote, version)
|
||||
elif is_remote_tag(module, dest, remote, version):
|
||||
cmd = 'git ls-remote %s -t refs/tags/%s' % (remote, version)
|
||||
else:
|
||||
# appears to be a sha1. return as-is since it appears
|
||||
# cannot check for a specific sha1 on remote
|
||||
return version
|
||||
(rc, out, err) = module.run_command(cmd, check_rc=True)
|
||||
if len(out) < 1:
|
||||
module.fail_json(msg="Could not determine remote revision for %s" % version)
|
||||
rev = out.split()[0]
|
||||
return rev
|
||||
|
||||
def is_remote_tag(module, dest, remote, version):
|
||||
os.chdir(dest)
|
||||
cmd = 'git ls-remote %s -t refs/tags/%s' % (remote, version)
|
||||
(rc, out, err) = module.run_command(cmd)
|
||||
if version in out:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_branches(module, dest):
|
||||
os.chdir(dest)
|
||||
branches = []
|
||||
(rc, out, err) = module.run_command("git branch -a")
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Could not determine branch data - received %s" % out)
|
||||
for line in out.split('\n'):
|
||||
branches.append(line.strip())
|
||||
return branches
|
||||
|
||||
def is_remote_branch(module, dest, remote, branch):
|
||||
branches = get_branches(module, dest)
|
||||
rbranch = 'remotes/%s/%s' % (remote, branch)
|
||||
if rbranch in branches:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_local_branch(module, dest, branch):
|
||||
branches = get_branches(module, dest)
|
||||
lbranch = '%s' % branch
|
||||
if lbranch in branches:
|
||||
return True
|
||||
elif '* %s' % branch in branches:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_current_branch(module, dest, branch):
|
||||
branches = get_branches(module, dest)
|
||||
for b in branches:
|
||||
if b.startswith('* '):
|
||||
cur_branch = b
|
||||
if branch == cur_branch or '* %s' % branch == cur_branch:
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
||||
def is_not_a_branch(module, dest):
|
||||
branches = get_branches(module, dest)
|
||||
for b in branches:
|
||||
if b.startswith('* ') and 'no branch' in b:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_head_branch(module, dest, remote):
|
||||
'''
|
||||
Determine what branch HEAD is associated with. This is partly
|
||||
taken from lib/ansible/utils/__init__.py. It finds the correct
|
||||
path to .git/HEAD and reads from that file the branch that HEAD is
|
||||
associated with. In the case of a detached HEAD, this will look
|
||||
up the branch in .git/refs/remotes/<remote>/HEAD.
|
||||
'''
|
||||
repo_path = os.path.join(dest, '.git')
|
||||
# Check if the .git is a file. If it is a file, it means that we are in a submodule structure.
|
||||
if os.path.isfile(repo_path):
|
||||
try:
|
||||
gitdir = yaml.safe_load(open(repo_path)).get('gitdir')
|
||||
# There is a posibility the .git file to have an absolute path.
|
||||
if os.path.isabs(gitdir):
|
||||
repo_path = gitdir
|
||||
else:
|
||||
repo_path = os.path.join(repo_path.split('.git')[0], gitdir)
|
||||
except (IOError, AttributeError):
|
||||
return ''
|
||||
# Read .git/HEAD for the name of the branch.
|
||||
# If we're in a detached HEAD state, look up the branch associated with
|
||||
# the remote HEAD in .git/refs/remotes/<remote>/HEAD
|
||||
f = open(os.path.join(repo_path, "HEAD"))
|
||||
if is_not_a_branch(module, dest):
|
||||
f.close()
|
||||
f = open(os.path.join(repo_path, 'refs', 'remotes', remote, 'HEAD'))
|
||||
branch = f.readline().split('/')[-1].rstrip("\n")
|
||||
f.close()
|
||||
return branch
|
||||
|
||||
def fetch(module, repo, dest, version, remote):
|
||||
''' updates repo from remote sources '''
|
||||
os.chdir(dest)
|
||||
(rc, out1, err1) = module.run_command("git fetch %s" % remote)
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed to download remote objects and refs")
|
||||
|
||||
(rc, out2, err2) = module.run_command("git fetch --tags %s" % remote)
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed to download remote objects and refs")
|
||||
return (rc, out1 + out2, err1 + err2)
|
||||
|
||||
def switch_version(module, dest, remote, version):
|
||||
''' once pulled, switch to a particular SHA, tag, or branch '''
|
||||
os.chdir(dest)
|
||||
cmd = ''
|
||||
if version != 'HEAD':
|
||||
if is_remote_branch(module, dest, remote, version):
|
||||
if not is_local_branch(module, dest, version):
|
||||
cmd = "git checkout --track -b %s %s/%s" % (version, remote, version)
|
||||
else:
|
||||
(rc, out, err) = module.run_command("git checkout --force %s" % version)
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed to checkout branch %s" % version)
|
||||
cmd = "git reset --hard %s/%s" % (remote, version)
|
||||
else:
|
||||
cmd = "git checkout --force %s" % version
|
||||
else:
|
||||
branch = get_head_branch(module, dest, remote)
|
||||
(rc, out, err) = module.run_command("git checkout --force %s" % branch)
|
||||
if rc != 0:
|
||||
module.fail_json(msg="Failed to checkout branch %s" % branch)
|
||||
cmd = "git reset --hard %s" % remote
|
||||
return module.run_command(cmd, check_rc=True)
|
||||
|
||||
# ===========================================
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
dest=dict(required=True),
|
||||
repo=dict(required=True, aliases=['name']),
|
||||
version=dict(default='HEAD'),
|
||||
remote=dict(default='origin'),
|
||||
force=dict(default='yes', type='bool'),
|
||||
depth=dict(default=None, type='int'),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
dest = os.path.abspath(os.path.expanduser(module.params['dest']))
|
||||
repo = module.params['repo']
|
||||
version = module.params['version']
|
||||
remote = module.params['remote']
|
||||
force = module.params['force']
|
||||
depth = module.params['depth']
|
||||
|
||||
gitconfig = os.path.join(dest, '.git', 'config')
|
||||
|
||||
rc, out, err, status = (0, None, None, None)
|
||||
|
||||
# if there is no git configuration, do a clone operation
|
||||
# else pull and switch the version
|
||||
before = None
|
||||
local_mods = False
|
||||
if not os.path.exists(gitconfig):
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
(rc, out, err) = clone(module, repo, dest, remote, depth)
|
||||
else:
|
||||
# else do a pull
|
||||
local_mods = has_local_mods(dest)
|
||||
before = get_version(dest)
|
||||
# if force, do a reset
|
||||
if local_mods and module.check_mode:
|
||||
module.exit_json(changed=True, msg='Local modifications exist')
|
||||
(rc, out, err) = reset(module,dest,force)
|
||||
if rc != 0:
|
||||
module.fail_json(msg=err)
|
||||
# check or get changes from remote
|
||||
remote_head = get_remote_head(module, dest, version, remote)
|
||||
if module.check_mode:
|
||||
changed = False
|
||||
if remote_head == version:
|
||||
# get_remote_head returned version as-is
|
||||
# were given a sha1 object, see if it is present
|
||||
(rc, out, err) = module.run_command("git show %s" % version)
|
||||
if version in out:
|
||||
changed = False
|
||||
else:
|
||||
changed = True
|
||||
else:
|
||||
remote_head = remote_head[0:7]
|
||||
if before != remote_head:
|
||||
changed = True
|
||||
else:
|
||||
changed = False
|
||||
module.exit_json(changed=changed, before=before, after=remote_head)
|
||||
(rc, out, err) = fetch(module, repo, dest, version, remote)
|
||||
if rc != 0:
|
||||
module.fail_json(msg=err)
|
||||
|
||||
# switch to version specified regardless of whether
|
||||
# we cloned or pulled
|
||||
(rc, out, err) = switch_version(module, dest, remote, version)
|
||||
|
||||
# determine if we changed anything
|
||||
after = get_version(dest)
|
||||
changed = False
|
||||
|
||||
if before != after or local_mods:
|
||||
changed = True
|
||||
|
||||
module.exit_json(changed=changed, before=before, after=after)
|
||||
|
||||
# include magic from lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
main()
|
||||
252
library/source_control/hg
Normal file
252
library/source_control/hg
Normal file
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Yeukhon Wong <yeukhon@acm.org>
|
||||
#
|
||||
# This module was originally inspired by Brad Olson's ansible-module-mercurial
|
||||
# <https://github.com/bradobro/ansible-module-mercurial>. This module tends
|
||||
# to follow the git module implementation.
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import ConfigParser
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: hg
|
||||
short_description: Manages Mercurial (hg) repositories.
|
||||
description:
|
||||
- Manages Mercurial (hg) repositories. Supports SSH, HTTP/S and local address.
|
||||
version_added: "1.0"
|
||||
author: Yeukhon Wong
|
||||
options:
|
||||
repo:
|
||||
description:
|
||||
- The repository address.
|
||||
required: true
|
||||
default: null
|
||||
dest:
|
||||
description:
|
||||
- Absolute path of where the repository should be cloned to.
|
||||
required: true
|
||||
default: null
|
||||
revision:
|
||||
description:
|
||||
- Equivalent C(-r) option in hg command which could be the changeset, revision number,
|
||||
branch name or even tag.
|
||||
required: false
|
||||
default: "default"
|
||||
force:
|
||||
description:
|
||||
- Discards uncommited changes. Runs C(hg update -C).
|
||||
required: false
|
||||
default: "yes"
|
||||
choices: [ "yes", "no" ]
|
||||
purge:
|
||||
description:
|
||||
- Delets untracked files. Runs C(hg purge). Note this requires C(purge) extension to
|
||||
be enabled if C(purge=yes). This module will modify hgrc file on behalf of the user
|
||||
and undo the changes before exiting the task.
|
||||
required: false
|
||||
default: "no"
|
||||
choices: [ "yes", "no" ]
|
||||
notes:
|
||||
- If the task seems to be hanging, first verify remote host is in C(known_hosts).
|
||||
SSH will prompt user to authorize the first contact with a remote host. One solution is to add
|
||||
C(StrictHostKeyChecking no) in C(.ssh/config) which will accept and authorize the connection
|
||||
on behalf of the user. However, if you run as a different user such as setting sudo to True),
|
||||
for example, root will not look at the user .ssh/config setting.
|
||||
examples:
|
||||
- code: "hg: repo=https://bitbucket.org/user/repo1 dest=/home/user/repo1 revision=stable purge=yes"
|
||||
description: Ensure the current working copy is inside the stable branch and deletes untracked files if any.
|
||||
|
||||
requirements: [ ]
|
||||
'''
|
||||
|
||||
def _set_hgrc(hgrc, vals):
|
||||
parser = ConfigParser.SafeConfigParser()
|
||||
parser.read(hgrc)
|
||||
|
||||
# val is a list of triple-tuple of the form [(section, option, value),...]
|
||||
for each in vals:
|
||||
(section, option, value) = each
|
||||
if not parser.has_section(section):
|
||||
parser.add_section(section)
|
||||
parser.set(section, option, value)
|
||||
|
||||
f = open(hgrc, 'w')
|
||||
parser.write(f)
|
||||
f.close()
|
||||
|
||||
def _undo_hgrc(hgrc, vals):
|
||||
parser = ConfigParser.SafeConfigParser()
|
||||
parser.read(hgrc)
|
||||
|
||||
for each in vals:
|
||||
(section, option, value) = each
|
||||
if parser.has_section(section):
|
||||
parser.remove_option(section, option)
|
||||
|
||||
f = open(hgrc, 'w')
|
||||
parser.write(f)
|
||||
f.close()
|
||||
|
||||
def _hg_command(module, args_list):
|
||||
(rc, out, err) = module.run_command(['hg'] + args_list)
|
||||
return (rc, out, err)
|
||||
|
||||
def _hg_list_untracked(module, dest):
|
||||
return _hg_command(module, ['purge', '-R', dest, '--print'])
|
||||
|
||||
def get_revision(module, dest):
|
||||
"""
|
||||
hg id -b -i -t returns a string in the format:
|
||||
"<changeset>[+] <branch_name> <tag>"
|
||||
This format lists the state of the current working copy,
|
||||
and indicates whether there are uncommitted changes by the
|
||||
plus sign. Otherwise, the sign is omitted.
|
||||
|
||||
Read the full description via hg id --help
|
||||
"""
|
||||
(rc, out, err) = _hg_command(module, ['id', '-b', '-i', '-t', '-R', dest])
|
||||
if rc != 0:
|
||||
module.fail_json(msg=err)
|
||||
else:
|
||||
return out.strip('\n')
|
||||
|
||||
def has_local_mods(module, dest):
|
||||
now = get_revision(module, dest)
|
||||
if '+' in now:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def hg_discard(module, dest):
|
||||
before = has_local_mods(module, dest)
|
||||
if not before:
|
||||
return False
|
||||
|
||||
(rc, out, err) = _hg_command(module, ['update', '-C', '-R', dest])
|
||||
if rc != 0:
|
||||
module.fail_json(msg=err)
|
||||
|
||||
after = has_local_mods(module, dest)
|
||||
if before != after and not after: # no more local modification
|
||||
return True
|
||||
|
||||
def hg_purge(module, dest):
|
||||
hgrc = os.path.join(dest, '.hg/hgrc')
|
||||
purge_option = [('extensions', 'purge', '')]
|
||||
_set_hgrc(hgrc, purge_option) # enable purge extension
|
||||
|
||||
# before purge, find out if there are any untracked files
|
||||
(rc1, out1, err1) = _hg_list_untracked(module, dest)
|
||||
if rc1 != 0:
|
||||
module.fail_json(msg=err)
|
||||
|
||||
# there are some untrackd files
|
||||
if out1 != '':
|
||||
(rc2, out2, err2) = _hg_command(module, ['purge', '-R', dest])
|
||||
if rc2 == 0:
|
||||
_undo_hgrc(hgrc, purge_option)
|
||||
else:
|
||||
module.fail_json(msg=err)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def hg_cleanup(module, dest, force, purge):
|
||||
discarded = False
|
||||
purged = False
|
||||
|
||||
if force:
|
||||
discarded = hg_discard(module, dest)
|
||||
if purge:
|
||||
purged = hg_purge(module, dest)
|
||||
if discarded or purged:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def hg_pull(module, dest, revision, repo):
|
||||
return _hg_command(module, ['pull', '-r', revision, '-R', dest, repo])
|
||||
|
||||
def hg_update(module, dest, revision):
|
||||
return _hg_command(module, ['update', '-R', dest])
|
||||
|
||||
def hg_clone(module, repo, dest, revision):
|
||||
return _hg_command(module, ['clone', repo, dest, '-r', revision])
|
||||
|
||||
def switch_version(module, dest, revision):
|
||||
return _hg_command(module, ['update', '-r', revision, '-R', dest])
|
||||
|
||||
# ===========================================
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
repo = dict(required=True),
|
||||
dest = dict(required=True),
|
||||
revision = dict(default="default"),
|
||||
force = dict(default='yes', type='bool'),
|
||||
purge = dict(default='no', type='bool')
|
||||
),
|
||||
)
|
||||
repo = module.params['repo']
|
||||
dest = module.params['dest']
|
||||
revision = module.params['revision']
|
||||
force = module.params['force']
|
||||
purge = module.params['purge']
|
||||
hgrc = os.path.join(dest, '.hg/hgrc')
|
||||
|
||||
# initial states
|
||||
before = ''
|
||||
changed = False
|
||||
cleaned = False
|
||||
|
||||
# If there is no hgrc file, then assume repo is absent
|
||||
# and perform clone. Otherwise, perform pull and update.
|
||||
if not os.path.exists(hgrc):
|
||||
(rc, out, err) = hg_clone(module, repo, dest, revision)
|
||||
if rc != 0:
|
||||
module.fail_json(msg=err)
|
||||
else:
|
||||
# get the current state before doing pulling
|
||||
before = get_revision(module, dest)
|
||||
|
||||
# can perform force and purge
|
||||
cleaned = hg_cleanup(module, dest, force, purge)
|
||||
|
||||
(rc, out, err) = hg_pull(module, dest, revision, repo)
|
||||
if rc != 0:
|
||||
module.fail_json(msg=err)
|
||||
|
||||
(rc, out, err) = hg_update(module, dest, revision)
|
||||
if rc != 0:
|
||||
module.fail_json(msg=err)
|
||||
|
||||
switch_version(module, dest, revision)
|
||||
after = get_revision(module, dest)
|
||||
if before != after or cleaned:
|
||||
changed = True
|
||||
module.exit_json(before=before, after=after, changed=changed, cleaned=cleaned)
|
||||
|
||||
# include magic from lib/ansible/module_common.py
|
||||
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
|
||||
main()
|
||||
190
library/source_control/subversion
Normal file
190
library/source_control/subversion
Normal file
@@ -0,0 +1,190 @@
|
||||
#!/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, module, dest, repo, revision, username, password):
|
||||
self.module = module
|
||||
self.dest = dest
|
||||
self.repo = repo
|
||||
self.revision = revision
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
def _exec(self, args):
|
||||
bits = ["svn --non-interactive --trust-server-cert --no-auth-cache", ]
|
||||
if self.username:
|
||||
bits.append("--username '%s'" % self.username)
|
||||
if self.password:
|
||||
bits.append("--password '%s'" % self.password)
|
||||
bits.append(args)
|
||||
rc, out, err = self.module.run_command(' '.join(bits), check_rc=True)
|
||||
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 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
|
||||
|
||||
|
||||
# ===========================================
|
||||
|
||||
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', type='bool'),
|
||||
username=dict(required=False),
|
||||
password=dict(required=False),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
dest = os.path.expanduser(module.params['dest'])
|
||||
repo = module.params['repo']
|
||||
revision = module.params['revision']
|
||||
force = module.params['force']
|
||||
username = module.params['username']
|
||||
password = module.params['password']
|
||||
|
||||
svn = Subversion(module, dest, repo, revision, username, password)
|
||||
|
||||
if not os.path.exists(dest):
|
||||
before = None
|
||||
local_mods = False
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
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.
|
||||
if module.check_mode:
|
||||
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()
|
||||
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()
|
||||
Reference in New Issue
Block a user