Initial commit

This commit is contained in:
Ansible Core Team 2020-03-09 09:11:07 +00:00
commit aebc1b03fd
4861 changed files with 812621 additions and 0 deletions

View file

@ -0,0 +1,284 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Evgeniy Krysanov <evgeniy.krysanov@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community',
}
DOCUMENTATION = r'''
---
module: bitbucket_access_key
short_description: Manages Bitbucket repository access keys
description:
- Manages Bitbucket repository access keys (also called deploy keys).
author:
- Evgeniy Krysanov (@catcombo)
options:
client_id:
description:
- The OAuth consumer key.
- If not set the environment variable C(BITBUCKET_CLIENT_ID) will be used.
type: str
client_secret:
description:
- The OAuth consumer secret.
- If not set the environment variable C(BITBUCKET_CLIENT_SECRET) will be used.
type: str
repository:
description:
- The repository name.
type: str
required: true
username:
description:
- The repository owner.
type: str
required: true
key:
description:
- The SSH public key.
type: str
label:
description:
- The key label.
type: str
required: true
state:
description:
- Indicates desired state of the access key.
type: str
required: true
choices: [ absent, present ]
notes:
- Bitbucket OAuth consumer key and secret can be obtained from Bitbucket profile -> Settings -> Access Management -> OAuth.
- Bitbucket OAuth consumer should have permissions to read and administrate account repositories.
- Check mode is supported.
'''
EXAMPLES = r'''
- name: Create access key
bitbucket_access_key:
repository: 'bitbucket-repo'
username: bitbucket_username
key: '{{lookup("file", "bitbucket.pub") }}'
label: 'Bitbucket'
state: present
- name: Delete access key
bitbucket_access_key:
repository: bitbucket-repo
username: bitbucket_username
label: Bitbucket
state: absent
'''
RETURN = r''' # '''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.source_control.bitbucket import BitbucketHelper
error_messages = {
'required_key': '`key` is required when the `state` is `present`',
'required_permission': 'OAuth consumer `client_id` should have permissions to read and administrate the repository',
'invalid_username_or_repo': 'Invalid `repository` or `username`',
'invalid_key': 'Invalid SSH key or key is already in use',
}
BITBUCKET_API_ENDPOINTS = {
'deploy-key-list': '%s/2.0/repositories/{username}/{repo_slug}/deploy-keys/' % BitbucketHelper.BITBUCKET_API_URL,
'deploy-key-detail': '%s/2.0/repositories/{username}/{repo_slug}/deploy-keys/{key_id}' % BitbucketHelper.BITBUCKET_API_URL,
}
def get_existing_deploy_key(module, bitbucket):
"""
Search for an existing deploy key on Bitbucket
with the label specified in module param `label`
:param module: instance of the :class:`AnsibleModule`
:param bitbucket: instance of the :class:`BitbucketHelper`
:return: existing deploy key or None if not found
:rtype: dict or None
Return example::
{
"id": 123,
"label": "mykey",
"created_on": "2019-03-23T10:15:21.517377+00:00",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAADA...AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5",
"type": "deploy_key",
"comment": "",
"last_used": None,
"repository": {
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/mleu/test"
},
"html": {
"href": "https://bitbucket.org/mleu/test"
},
"avatar": {
"href": "..."
}
},
"type": "repository",
"name": "test",
"full_name": "mleu/test",
"uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}"
},
"links": {
"self": {
"href": "https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123"
}
},
}
"""
content = {
'next': BITBUCKET_API_ENDPOINTS['deploy-key-list'].format(
username=module.params['username'],
repo_slug=module.params['repository'],
)
}
# Look through the all response pages in search of deploy key we need
while 'next' in content:
info, content = bitbucket.request(
api_url=content['next'],
method='GET',
)
if info['status'] == 404:
module.fail_json(msg=error_messages['invalid_username_or_repo'])
if info['status'] == 403:
module.fail_json(msg=error_messages['required_permission'])
if info['status'] != 200:
module.fail_json(msg='Failed to retrieve the list of deploy keys: {0}'.format(info))
res = next(iter(filter(lambda v: v['label'] == module.params['label'], content['values'])), None)
if res is not None:
return res
return None
def create_deploy_key(module, bitbucket):
info, content = bitbucket.request(
api_url=BITBUCKET_API_ENDPOINTS['deploy-key-list'].format(
username=module.params['username'],
repo_slug=module.params['repository'],
),
method='POST',
data={
'key': module.params['key'],
'label': module.params['label'],
},
)
if info['status'] == 404:
module.fail_json(msg=error_messages['invalid_username_or_repo'])
if info['status'] == 403:
module.fail_json(msg=error_messages['required_permission'])
if info['status'] == 400:
module.fail_json(msg=error_messages['invalid_key'])
if info['status'] != 200:
module.fail_json(msg='Failed to create deploy key `{label}`: {info}'.format(
label=module.params['label'],
info=info,
))
def delete_deploy_key(module, bitbucket, key_id):
info, content = bitbucket.request(
api_url=BITBUCKET_API_ENDPOINTS['deploy-key-detail'].format(
username=module.params['username'],
repo_slug=module.params['repository'],
key_id=key_id,
),
method='DELETE',
)
if info['status'] == 404:
module.fail_json(msg=error_messages['invalid_username_or_repo'])
if info['status'] == 403:
module.fail_json(msg=error_messages['required_permission'])
if info['status'] != 204:
module.fail_json(msg='Failed to delete deploy key `{label}`: {info}'.format(
label=module.params['label'],
info=info,
))
def main():
argument_spec = BitbucketHelper.bitbucket_argument_spec()
argument_spec.update(
repository=dict(type='str', required=True),
username=dict(type='str', required=True),
key=dict(type='str'),
label=dict(type='str', required=True),
state=dict(type='str', choices=['present', 'absent'], required=True),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
bitbucket = BitbucketHelper(module)
key = module.params['key']
state = module.params['state']
# Check parameters
if (key is None) and (state == 'present'):
module.fail_json(msg=error_messages['required_key'])
# Retrieve access token for authorized API requests
bitbucket.fetch_access_token()
# Retrieve existing deploy key (if any)
existing_deploy_key = get_existing_deploy_key(module, bitbucket)
changed = False
# Create new deploy key in case it doesn't exists
if not existing_deploy_key and (state == 'present'):
if not module.check_mode:
create_deploy_key(module, bitbucket)
changed = True
# Update deploy key if the old value does not match the new one
elif existing_deploy_key and (state == 'present'):
if not key.startswith(existing_deploy_key.get('key')):
if not module.check_mode:
# Bitbucket doesn't support update key for the same label,
# so we need to delete the old one first
delete_deploy_key(module, bitbucket, existing_deploy_key['id'])
create_deploy_key(module, bitbucket)
changed = True
# Delete deploy key
elif existing_deploy_key and (state == 'absent'):
if not module.check_mode:
delete_deploy_key(module, bitbucket, existing_deploy_key['id'])
changed = True
module.exit_json(changed=changed)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,212 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Evgeniy Krysanov <evgeniy.krysanov@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community',
}
DOCUMENTATION = r'''
---
module: bitbucket_pipeline_key_pair
short_description: Manages Bitbucket pipeline SSH key pair
description:
- Manages Bitbucket pipeline SSH key pair.
author:
- Evgeniy Krysanov (@catcombo)
options:
client_id:
description:
- OAuth consumer key.
- If not set the environment variable C(BITBUCKET_CLIENT_ID) will be used.
type: str
client_secret:
description:
- OAuth consumer secret.
- If not set the environment variable C(BITBUCKET_CLIENT_SECRET) will be used.
type: str
repository:
description:
- The repository name.
type: str
required: true
username:
description:
- The repository owner.
type: str
required: true
public_key:
description:
- The public key.
type: str
private_key:
description:
- The private key.
type: str
state:
description:
- Indicates desired state of the key pair.
type: str
required: true
choices: [ absent, present ]
notes:
- Bitbucket OAuth consumer key and secret can be obtained from Bitbucket profile -> Settings -> Access Management -> OAuth.
- Check mode is supported.
'''
EXAMPLES = r'''
- name: Create or update SSH key pair
bitbucket_pipeline_key_pair:
repository: 'bitbucket-repo'
username: bitbucket_username
public_key: '{{lookup("file", "bitbucket.pub") }}'
private_key: '{{lookup("file", "bitbucket") }}'
state: present
- name: Remove SSH key pair
bitbucket_pipeline_key_pair:
repository: bitbucket-repo
username: bitbucket_username
state: absent
'''
RETURN = r''' # '''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.source_control.bitbucket import BitbucketHelper
error_messages = {
'invalid_params': 'Account, repository or SSH key pair was not found',
'required_keys': '`public_key` and `private_key` are required when the `state` is `present`',
}
BITBUCKET_API_ENDPOINTS = {
'ssh-key-pair': '%s/2.0/repositories/{username}/{repo_slug}/pipelines_config/ssh/key_pair' % BitbucketHelper.BITBUCKET_API_URL,
}
def get_existing_ssh_key_pair(module, bitbucket):
"""
Retrieves an existing ssh key pair from repository
specified in module param `repository`
:param module: instance of the :class:`AnsibleModule`
:param bitbucket: instance of the :class:`BitbucketHelper`
:return: existing key pair or None if not found
:rtype: dict or None
Return example::
{
"public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ...2E8HAeT",
"type": "pipeline_ssh_key_pair"
}
"""
api_url = BITBUCKET_API_ENDPOINTS['ssh-key-pair'].format(
username=module.params['username'],
repo_slug=module.params['repository'],
)
info, content = bitbucket.request(
api_url=api_url,
method='GET',
)
if info['status'] == 404:
# Account, repository or SSH key pair was not found.
return None
return content
def update_ssh_key_pair(module, bitbucket):
info, content = bitbucket.request(
api_url=BITBUCKET_API_ENDPOINTS['ssh-key-pair'].format(
username=module.params['username'],
repo_slug=module.params['repository'],
),
method='PUT',
data={
'private_key': module.params['private_key'],
'public_key': module.params['public_key'],
},
)
if info['status'] == 404:
module.fail_json(msg=error_messages['invalid_params'])
if info['status'] != 200:
module.fail_json(msg='Failed to create or update pipeline ssh key pair : {0}'.format(info))
def delete_ssh_key_pair(module, bitbucket):
info, content = bitbucket.request(
api_url=BITBUCKET_API_ENDPOINTS['ssh-key-pair'].format(
username=module.params['username'],
repo_slug=module.params['repository'],
),
method='DELETE',
)
if info['status'] == 404:
module.fail_json(msg=error_messages['invalid_params'])
if info['status'] != 204:
module.fail_json(msg='Failed to delete pipeline ssh key pair: {0}'.format(info))
def main():
argument_spec = BitbucketHelper.bitbucket_argument_spec()
argument_spec.update(
repository=dict(type='str', required=True),
username=dict(type='str', required=True),
public_key=dict(type='str'),
private_key=dict(type='str', no_log=True),
state=dict(type='str', choices=['present', 'absent'], required=True),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
bitbucket = BitbucketHelper(module)
state = module.params['state']
public_key = module.params['public_key']
private_key = module.params['private_key']
# Check parameters
if ((public_key is None) or (private_key is None)) and (state == 'present'):
module.fail_json(msg=error_messages['required_keys'])
# Retrieve access token for authorized API requests
bitbucket.fetch_access_token()
# Retrieve existing ssh key
key_pair = get_existing_ssh_key_pair(module, bitbucket)
changed = False
# Create or update key pair
if (not key_pair or (key_pair.get('public_key') != public_key)) and (state == 'present'):
if not module.check_mode:
update_ssh_key_pair(module, bitbucket)
changed = True
# Delete key pair
elif key_pair and (state == 'absent'):
if not module.check_mode:
delete_ssh_key_pair(module, bitbucket)
changed = True
module.exit_json(changed=changed)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,309 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Evgeniy Krysanov <evgeniy.krysanov@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community',
}
DOCUMENTATION = r'''
---
module: bitbucket_pipeline_known_host
short_description: Manages Bitbucket pipeline known hosts
description:
- Manages Bitbucket pipeline known hosts under the "SSH Keys" menu.
- The host fingerprint will be retrieved automatically, but in case of an error, one can use I(key) field to specify it manually.
author:
- Evgeniy Krysanov (@catcombo)
requirements:
- paramiko
options:
client_id:
description:
- The OAuth consumer key.
- If not set the environment variable C(BITBUCKET_CLIENT_ID) will be used.
type: str
client_secret:
description:
- The OAuth consumer secret.
- If not set the environment variable C(BITBUCKET_CLIENT_SECRET) will be used.
type: str
repository:
description:
- The repository name.
type: str
required: true
username:
description:
- The repository owner.
type: str
required: true
name:
description:
- The FQDN of the known host.
type: str
required: true
key:
description:
- The public key.
type: str
state:
description:
- Indicates desired state of the record.
type: str
required: true
choices: [ absent, present ]
notes:
- Bitbucket OAuth consumer key and secret can be obtained from Bitbucket profile -> Settings -> Access Management -> OAuth.
- Check mode is supported.
'''
EXAMPLES = r'''
- name: Create known hosts from the list
bitbucket_pipeline_known_host:
repository: 'bitbucket-repo'
username: bitbucket_username
name: '{{ item }}'
state: present
with_items:
- bitbucket.org
- example.com
- name: Remove known host
bitbucket_pipeline_known_host:
repository: bitbucket-repo
username: bitbucket_username
name: bitbucket.org
state: absent
- name: Specify public key file
bitbucket_pipeline_known_host:
repository: bitbucket-repo
username: bitbucket_username
name: bitbucket.org
key: '{{lookup("file", "bitbucket.pub") }}'
state: absent
'''
RETURN = r''' # '''
import socket
try:
import paramiko
HAS_PARAMIKO = True
except ImportError:
HAS_PARAMIKO = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.source_control.bitbucket import BitbucketHelper
error_messages = {
'invalid_params': 'Account or repository was not found',
'unknown_key_type': 'Public key type is unknown',
}
BITBUCKET_API_ENDPOINTS = {
'known-host-list': '%s/2.0/repositories/{username}/{repo_slug}/pipelines_config/ssh/known_hosts/' % BitbucketHelper.BITBUCKET_API_URL,
'known-host-detail': '%s/2.0/repositories/{username}/{repo_slug}/pipelines_config/ssh/known_hosts/{known_host_uuid}' % BitbucketHelper.BITBUCKET_API_URL,
}
def get_existing_known_host(module, bitbucket):
"""
Search for a host in Bitbucket pipelines known hosts
with the name specified in module param `name`
:param module: instance of the :class:`AnsibleModule`
:param bitbucket: instance of the :class:`BitbucketHelper`
:return: existing host or None if not found
:rtype: dict or None
Return example::
{
'type': 'pipeline_known_host',
'uuid': '{21cc0590-bebe-4fae-8baf-03722704119a7}'
'hostname': 'bitbucket.org',
'public_key': {
'type': 'pipeline_ssh_public_key',
'md5_fingerprint': 'md5:97:8c:1b:f2:6f:14:6b:4b:3b:ec:aa:46:46:74:7c:40',
'sha256_fingerprint': 'SHA256:zzXQOXSFBEiUtuE8AikoYKwbHaxvSc0ojez9YXaGp1A',
'key_type': 'ssh-rsa',
'key': 'AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kN...seeFVBoGqzHM9yXw=='
},
}
"""
content = {
'next': BITBUCKET_API_ENDPOINTS['known-host-list'].format(
username=module.params['username'],
repo_slug=module.params['repository'],
)
}
# Look through all response pages in search of hostname we need
while 'next' in content:
info, content = bitbucket.request(
api_url=content['next'],
method='GET',
)
if info['status'] == 404:
module.fail_json(msg='Invalid `repository` or `username`.')
if info['status'] != 200:
module.fail_json(msg='Failed to retrieve list of known hosts: {0}'.format(info))
host = next(filter(lambda v: v['hostname'] == module.params['name'], content['values']), None)
if host is not None:
return host
return None
def get_host_key(module, hostname):
"""
Fetches public key for specified host
:param module: instance of the :class:`AnsibleModule`
:param hostname: host name
:return: key type and key content
:rtype: tuple
Return example::
(
'ssh-rsa',
'AAAAB3NzaC1yc2EAAAABIwAAA...SBne8+seeFVBoGqzHM9yXw==',
)
"""
try:
sock = socket.socket()
sock.connect((hostname, 22))
except socket.error:
module.fail_json(msg='Error opening socket to {0}'.format(hostname))
try:
trans = paramiko.transport.Transport(sock)
trans.start_client()
host_key = trans.get_remote_server_key()
except paramiko.SSHException:
module.fail_json(msg='SSH error on retrieving {0} server key'.format(hostname))
trans.close()
sock.close()
key_type = host_key.get_name()
key = host_key.get_base64()
return key_type, key
def create_known_host(module, bitbucket):
hostname = module.params['name']
key_param = module.params['key']
if key_param is None:
key_type, key = get_host_key(module, hostname)
elif ' ' in key_param:
key_type, key = key_param.split(' ', 1)
else:
module.fail_json(msg=error_messages['unknown_key_type'])
info, content = bitbucket.request(
api_url=BITBUCKET_API_ENDPOINTS['known-host-list'].format(
username=module.params['username'],
repo_slug=module.params['repository'],
),
method='POST',
data={
'hostname': hostname,
'public_key': {
'key_type': key_type,
'key': key,
}
},
)
if info['status'] == 404:
module.fail_json(msg=error_messages['invalid_params'])
if info['status'] != 201:
module.fail_json(msg='Failed to create known host `{hostname}`: {info}'.format(
hostname=module.params['hostname'],
info=info,
))
def delete_known_host(module, bitbucket, known_host_uuid):
info, content = bitbucket.request(
api_url=BITBUCKET_API_ENDPOINTS['known-host-detail'].format(
username=module.params['username'],
repo_slug=module.params['repository'],
known_host_uuid=known_host_uuid,
),
method='DELETE',
)
if info['status'] == 404:
module.fail_json(msg=error_messages['invalid_params'])
if info['status'] != 204:
module.fail_json(msg='Failed to delete known host `{hostname}`: {info}'.format(
hostname=module.params['name'],
info=info,
))
def main():
argument_spec = BitbucketHelper.bitbucket_argument_spec()
argument_spec.update(
repository=dict(type='str', required=True),
username=dict(type='str', required=True),
name=dict(type='str', required=True),
key=dict(type='str'),
state=dict(type='str', choices=['present', 'absent'], required=True),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
if (module.params['key'] is None) and (not HAS_PARAMIKO):
module.fail_json(msg='`paramiko` package not found, please install it.')
bitbucket = BitbucketHelper(module)
# Retrieve access token for authorized API requests
bitbucket.fetch_access_token()
# Retrieve existing known host
existing_host = get_existing_known_host(module, bitbucket)
state = module.params['state']
changed = False
# Create new host in case it doesn't exists
if not existing_host and (state == 'present'):
if not module.check_mode:
create_known_host(module, bitbucket)
changed = True
# Delete host
elif existing_host and (state == 'absent'):
if not module.check_mode:
delete_known_host(module, bitbucket, existing_host['uuid'])
changed = True
module.exit_json(changed=changed)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,271 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Evgeniy Krysanov <evgeniy.krysanov@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community',
}
DOCUMENTATION = r'''
---
module: bitbucket_pipeline_variable
short_description: Manages Bitbucket pipeline variables
description:
- Manages Bitbucket pipeline variables.
author:
- Evgeniy Krysanov (@catcombo)
options:
client_id:
description:
- The OAuth consumer key.
- If not set the environment variable C(BITBUCKET_CLIENT_ID) will be used.
type: str
client_secret:
description:
- The OAuth consumer secret.
- If not set the environment variable C(BITBUCKET_CLIENT_SECRET) will be used.
type: str
repository:
description:
- The repository name.
type: str
required: true
username:
description:
- The repository owner.
type: str
required: true
name:
description:
- The pipeline variable name.
type: str
required: true
value:
description:
- The pipeline variable value.
type: str
secured:
description:
- Whether to encrypt the variable value.
type: bool
default: no
state:
description:
- Indicates desired state of the variable.
type: str
required: true
choices: [ absent, present ]
notes:
- Bitbucket OAuth consumer key and secret can be obtained from Bitbucket profile -> Settings -> Access Management -> OAuth.
- Check mode is supported.
- For secured values return parameter C(changed) is always C(True).
'''
EXAMPLES = r'''
- name: Create or update pipeline variables from the list
bitbucket_pipeline_variable:
repository: 'bitbucket-repo'
username: bitbucket_username
name: '{{ item.name }}'
value: '{{ item.value }}'
secured: '{{ item.secured }}'
state: present
with_items:
- { name: AWS_ACCESS_KEY, value: ABCD1234 }
- { name: AWS_SECRET, value: qwe789poi123vbn0, secured: True }
- name: Remove pipeline variable
bitbucket_pipeline_variable:
repository: bitbucket-repo
username: bitbucket_username
name: AWS_ACCESS_KEY
state: absent
'''
RETURN = r''' # '''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.source_control.bitbucket import BitbucketHelper
error_messages = {
'required_value': '`value` is required when the `state` is `present`',
}
BITBUCKET_API_ENDPOINTS = {
'pipeline-variable-list': '%s/2.0/repositories/{username}/{repo_slug}/pipelines_config/variables/' % BitbucketHelper.BITBUCKET_API_URL,
'pipeline-variable-detail': '%s/2.0/repositories/{username}/{repo_slug}/pipelines_config/variables/{variable_uuid}' % BitbucketHelper.BITBUCKET_API_URL,
}
def get_existing_pipeline_variable(module, bitbucket):
"""
Search for a pipeline variable
:param module: instance of the :class:`AnsibleModule`
:param bitbucket: instance of the :class:`BitbucketHelper`
:return: existing variable or None if not found
:rtype: dict or None
Return example::
{
'name': 'AWS_ACCESS_OBKEY_ID',
'value': 'x7HU80-a2',
'type': 'pipeline_variable',
'secured': False,
'uuid': '{9ddb0507-439a-495a-99f3-5464f15128127}'
}
The `value` key in dict is absent in case of secured variable.
"""
content = {
'next': BITBUCKET_API_ENDPOINTS['pipeline-variable-list'].format(
username=module.params['username'],
repo_slug=module.params['repository'],
)
}
# Look through the all response pages in search of variable we need
while 'next' in content:
info, content = bitbucket.request(
api_url=content['next'],
method='GET',
)
if info['status'] == 404:
module.fail_json(msg='Invalid `repository` or `username`.')
if info['status'] != 200:
module.fail_json(msg='Failed to retrieve the list of pipeline variables: {0}'.format(info))
var = next(filter(lambda v: v['key'] == module.params['name'], content['values']), None)
if var is not None:
var['name'] = var.pop('key')
return var
return None
def create_pipeline_variable(module, bitbucket):
info, content = bitbucket.request(
api_url=BITBUCKET_API_ENDPOINTS['pipeline-variable-list'].format(
username=module.params['username'],
repo_slug=module.params['repository'],
),
method='POST',
data={
'key': module.params['name'],
'value': module.params['value'],
'secured': module.params['secured'],
},
)
if info['status'] != 201:
module.fail_json(msg='Failed to create pipeline variable `{name}`: {info}'.format(
name=module.params['name'],
info=info,
))
def update_pipeline_variable(module, bitbucket, variable_uuid):
info, content = bitbucket.request(
api_url=BITBUCKET_API_ENDPOINTS['pipeline-variable-detail'].format(
username=module.params['username'],
repo_slug=module.params['repository'],
variable_uuid=variable_uuid,
),
method='PUT',
data={
'value': module.params['value'],
'secured': module.params['secured'],
},
)
if info['status'] != 200:
module.fail_json(msg='Failed to update pipeline variable `{name}`: {info}'.format(
name=module.params['name'],
info=info,
))
def delete_pipeline_variable(module, bitbucket, variable_uuid):
info, content = bitbucket.request(
api_url=BITBUCKET_API_ENDPOINTS['pipeline-variable-detail'].format(
username=module.params['username'],
repo_slug=module.params['repository'],
variable_uuid=variable_uuid,
),
method='DELETE',
)
if info['status'] != 204:
module.fail_json(msg='Failed to delete pipeline variable `{name}`: {info}'.format(
name=module.params['name'],
info=info,
))
def main():
argument_spec = BitbucketHelper.bitbucket_argument_spec()
argument_spec.update(
repository=dict(type='str', required=True),
username=dict(type='str', required=True),
name=dict(type='str', required=True),
value=dict(type='str'),
secured=dict(type='bool', default=False),
state=dict(type='str', choices=['present', 'absent'], required=True),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
bitbucket = BitbucketHelper(module)
value = module.params['value']
state = module.params['state']
secured = module.params['secured']
# Check parameters
if (value is None) and (state == 'present'):
module.fail_json(msg=error_messages['required_value'])
# Retrieve access token for authorized API requests
bitbucket.fetch_access_token()
# Retrieve existing pipeline variable (if any)
existing_variable = get_existing_pipeline_variable(module, bitbucket)
changed = False
# Create new variable in case it doesn't exists
if not existing_variable and (state == 'present'):
if not module.check_mode:
create_pipeline_variable(module, bitbucket)
changed = True
# Update variable if it is secured or the old value does not match the new one
elif existing_variable and (state == 'present'):
if (existing_variable['secured'] != secured) or (existing_variable.get('value') != value):
if not module.check_mode:
update_pipeline_variable(module, bitbucket, existing_variable['uuid'])
changed = True
# Delete variable
elif existing_variable and (state == 'absent'):
if not module.check_mode:
delete_pipeline_variable(module, bitbucket, existing_variable['uuid'])
changed = True
module.exit_json(changed=changed)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,194 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2013, André Paramés <git@andreparames.com>
# Based on the Git module by Michael DeHaan <michael.dehaan@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: bzr
author:
- André Paramés (@andreparames)
short_description: Deploy software (or files) from bzr branches
description:
- Manage I(bzr) branches to deploy files or software.
options:
name:
description:
- SSH or HTTP protocol address of the parent branch.
aliases: [ parent ]
required: yes
dest:
description:
- Absolute path of where the branch should be cloned to.
required: yes
version:
description:
- What version of the branch to clone. This can be the
bzr revno or revid.
default: head
force:
description:
- If C(yes), any modified files in the working
tree will be discarded. Before 1.9 the default
value was C(yes).
type: bool
default: 'no'
executable:
description:
- Path to bzr executable to use. If not supplied,
the normal mechanism for resolving binary paths will be used.
'''
EXAMPLES = '''
# Example bzr checkout from Ansible Playbooks
- bzr:
name: bzr+ssh://foosball.example.org/path/to/branch
dest: /srv/checkout
version: 22
'''
import os
import re
from ansible.module_utils.basic import AnsibleModule
class Bzr(object):
def __init__(self, module, parent, dest, version, bzr_path):
self.module = module
self.parent = parent
self.dest = dest
self.version = version
self.bzr_path = bzr_path
def _command(self, args_list, cwd=None, **kwargs):
(rc, out, err) = self.module.run_command([self.bzr_path] + args_list, cwd=cwd, **kwargs)
return (rc, out, err)
def get_version(self):
'''samples the version of the bzr branch'''
cmd = "%s revno" % self.bzr_path
rc, stdout, stderr = self.module.run_command(cmd, cwd=self.dest)
revno = stdout.strip()
return revno
def clone(self):
'''makes a new bzr branch if it does not already exist'''
dest_dirname = os.path.dirname(self.dest)
try:
os.makedirs(dest_dirname)
except Exception:
pass
if self.version.lower() != 'head':
args_list = ["branch", "-r", self.version, self.parent, self.dest]
else:
args_list = ["branch", self.parent, self.dest]
return self._command(args_list, check_rc=True, cwd=dest_dirname)
def has_local_mods(self):
cmd = "%s status -S" % self.bzr_path
rc, stdout, stderr = self.module.run_command(cmd, cwd=self.dest)
lines = stdout.splitlines()
lines = filter(lambda c: not re.search('^\\?\\?.*$', c), lines)
return len(lines) > 0
def reset(self, force):
'''
Resets the index and working tree to head.
Discards any changes to tracked files in the working
tree since that commit.
'''
if not force and self.has_local_mods():
self.module.fail_json(msg="Local modifications exist in branch (force=no).")
return self._command(["revert"], check_rc=True, cwd=self.dest)
def fetch(self):
'''updates branch from remote sources'''
if self.version.lower() != 'head':
(rc, out, err) = self._command(["pull", "-r", self.version], cwd=self.dest)
else:
(rc, out, err) = self._command(["pull"], cwd=self.dest)
if rc != 0:
self.module.fail_json(msg="Failed to pull")
return (rc, out, err)
def switch_version(self):
'''once pulled, switch to a particular revno or revid'''
if self.version.lower() != 'head':
args_list = ["revert", "-r", self.version]
else:
args_list = ["revert"]
return self._command(args_list, check_rc=True, cwd=self.dest)
# ===========================================
def main():
module = AnsibleModule(
argument_spec=dict(
dest=dict(type='path', required=True),
name=dict(type='str', required=True, aliases=['parent']),
version=dict(type='str', default='head'),
force=dict(type='bool', default='no'),
executable=dict(type='str'),
)
)
dest = module.params['dest']
parent = module.params['name']
version = module.params['version']
force = module.params['force']
bzr_path = module.params['executable'] or module.get_bin_path('bzr', True)
bzrconfig = os.path.join(dest, '.bzr', 'branch', 'branch.conf')
rc, out, err = (0, None, None)
bzr = Bzr(module, parent, dest, version, bzr_path)
# 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) = bzr.clone()
else:
# else do a pull
local_mods = bzr.has_local_mods()
before = bzr.get_version()
(rc, out, err) = bzr.reset(force)
if rc != 0:
module.fail_json(msg=err)
(rc, out, err) = bzr.fetch()
if rc != 0:
module.fail_json(msg=err)
# switch to version specified regardless of whether
# we cloned or pulled
(rc, out, err) = bzr.switch_version()
# determine if we changed anything
after = bzr.get_version()
changed = False
if before != after or local_mods:
changed = True
module.exit_json(changed=changed, before=before, after=after)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,267 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2015, Marius Gedminas <marius@pov.lt>
# (c) 2016, Matthew Gamble <git@matthewgamble.net>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: git_config
author:
- Matthew Gamble (@djmattyg007)
- Marius Gedminas (@mgedmin)
requirements: ['git']
short_description: Read and write git configuration
description:
- The C(git_config) module changes git configuration by invoking 'git config'.
This is needed if you don't want to use M(template) for the entire git
config file (e.g. because you need to change just C(user.email) in
/etc/.git/config). Solutions involving M(command) are cumbersome or
don't work correctly in check mode.
options:
list_all:
description:
- List all settings (optionally limited to a given I(scope))
type: bool
default: 'no'
name:
description:
- The name of the setting. If no value is supplied, the value will
be read from the config if it has been set.
repo:
description:
- Path to a git repository for reading and writing values from a
specific repo.
scope:
description:
- Specify which scope to read/set values from. This is required
when setting config values. If this is set to local, you must
also specify the repo parameter. It defaults to system only when
not using I(list_all)=yes.
choices: [ "local", "global", "system" ]
state:
description:
- "Indicates the setting should be set/unset.
This parameter has higher precedence than I(value) parameter:
when I(state)=absent and I(value) is defined, I(value) is discarded."
choices: [ 'present', 'absent' ]
default: 'present'
value:
description:
- When specifying the name of a single setting, supply a value to
set that setting to the given value.
'''
EXAMPLES = '''
# Set some settings in ~/.gitconfig
- git_config:
name: alias.ci
scope: global
value: commit
- git_config:
name: alias.st
scope: global
value: status
# Unset some settings in ~/.gitconfig
- git_config:
name: alias.ci
scope: global
state: absent
# Or system-wide:
- git_config:
name: alias.remotev
scope: system
value: remote -v
- git_config:
name: core.editor
scope: global
value: vim
# scope=system is the default
- git_config:
name: alias.diffc
value: diff --cached
- git_config:
name: color.ui
value: auto
# Make etckeeper not complain when invoked by cron
- git_config:
name: user.email
repo: /etc
scope: local
value: 'root@{{ ansible_fqdn }}'
# Read individual values from git config
- git_config:
name: alias.ci
scope: global
# scope: system is also assumed when reading values, unless list_all=yes
- git_config:
name: alias.diffc
# Read all values from git config
- git_config:
list_all: yes
scope: global
# When list_all=yes and no scope is specified, you get configuration from all scopes
- git_config:
list_all: yes
# Specify a repository to include local settings
- git_config:
list_all: yes
repo: /path/to/repo.git
'''
RETURN = '''
---
config_value:
description: When list_all=no and value is not set, a string containing the value of the setting in name
returned: success
type: str
sample: "vim"
config_values:
description: When list_all=yes, a dict containing key/value pairs of multiple configuration settings
returned: success
type: dict
sample:
core.editor: "vim"
color.ui: "auto"
alias.diffc: "diff --cached"
alias.remotev: "remote -v"
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six.moves import shlex_quote
def main():
module = AnsibleModule(
argument_spec=dict(
list_all=dict(required=False, type='bool', default=False),
name=dict(type='str'),
repo=dict(type='path'),
scope=dict(required=False, type='str', choices=['local', 'global', 'system']),
state=dict(required=False, type='str', default='present', choices=['present', 'absent']),
value=dict(required=False)
),
mutually_exclusive=[['list_all', 'name'], ['list_all', 'value'], ['list_all', 'state']],
required_if=[('scope', 'local', ['repo'])],
required_one_of=[['list_all', 'name']],
supports_check_mode=True,
)
git_path = module.get_bin_path('git', True)
params = module.params
# We check error message for a pattern, so we need to make sure the messages appear in the form we're expecting.
# Set the locale to C to ensure consistent messages.
module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
if params['name']:
name = params['name']
else:
name = None
if params['scope']:
scope = params['scope']
elif params['list_all']:
scope = None
else:
scope = 'system'
if params['state'] == 'absent':
unset = 'unset'
params['value'] = None
else:
unset = None
if params['value']:
new_value = params['value']
else:
new_value = None
args = [git_path, "config", "--includes"]
if params['list_all']:
args.append('-l')
if scope:
args.append("--" + scope)
if name:
args.append(name)
if scope == 'local':
dir = params['repo']
elif params['list_all'] and params['repo']:
# Include local settings from a specific repo when listing all available settings
dir = params['repo']
else:
# Run from root directory to avoid accidentally picking up any local config settings
dir = "/"
(rc, out, err) = module.run_command(' '.join(args), cwd=dir)
if params['list_all'] and scope and rc == 128 and 'unable to read config file' in err:
# This just means nothing has been set at the given scope
module.exit_json(changed=False, msg='', config_values={})
elif rc >= 2:
# If the return code is 1, it just means the option hasn't been set yet, which is fine.
module.fail_json(rc=rc, msg=err, cmd=' '.join(args))
if params['list_all']:
values = out.rstrip().splitlines()
config_values = {}
for value in values:
k, v = value.split('=', 1)
config_values[k] = v
module.exit_json(changed=False, msg='', config_values=config_values)
elif not new_value and not unset:
module.exit_json(changed=False, msg='', config_value=out.rstrip())
elif unset and not out:
module.exit_json(changed=False, msg='no setting to unset')
else:
old_value = out.rstrip()
if old_value == new_value:
module.exit_json(changed=False, msg="")
if not module.check_mode:
if unset:
args.insert(len(args) - 1, "--" + unset)
cmd = ' '.join(args)
else:
new_value_quoted = shlex_quote(new_value)
cmd = ' '.join(args + [new_value_quoted])
(rc, out, err) = module.run_command(cmd, cwd=dir)
if err:
module.fail_json(rc=rc, msg=err, cmd=cmd)
module.exit_json(
msg='setting changed',
diff=dict(
before_header=' '.join(args),
before=old_value + "\n",
after_header=' '.join(args),
after=(new_value or '') + "\n"
),
changed=True
)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,333 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: github_deploy_key
author: "Ali (@bincyber)"
short_description: Manages deploy keys for GitHub repositories.
description:
- "Adds or removes deploy keys for GitHub repositories. Supports authentication using username and password,
username and password and 2-factor authentication code (OTP), OAuth2 token, or personal access token. Admin
rights on the repository are required."
options:
github_url:
description:
- The base URL of the GitHub API
required: false
type: str
default: https://api.github.com
owner:
description:
- The name of the individual account or organization that owns the GitHub repository.
required: true
aliases: [ 'account', 'organization' ]
repo:
description:
- The name of the GitHub repository.
required: true
aliases: [ 'repository' ]
name:
description:
- The name for the deploy key.
required: true
aliases: [ 'title', 'label' ]
key:
description:
- The SSH public key to add to the repository as a deploy key.
required: true
read_only:
description:
- If C(true), the deploy key will only be able to read repository contents. Otherwise, the deploy key will be able to read and write.
type: bool
default: 'yes'
state:
description:
- The state of the deploy key.
default: "present"
choices: [ "present", "absent" ]
force:
description:
- If C(true), forcefully adds the deploy key by deleting any existing deploy key with the same public key or title.
type: bool
default: 'no'
username:
description:
- The username to authenticate with. Should not be set when using personal access token
password:
description:
- The password to authenticate with. Alternatively, a personal access token can be used instead of I(username) and I(password) combination.
token:
description:
- The OAuth2 token or personal access token to authenticate with. Mutually exclusive with I(password).
otp:
description:
- The 6 digit One Time Password for 2-Factor Authentication. Required together with I(username) and I(password).
aliases: ['2fa_token']
notes:
- "Refer to GitHub's API documentation here: https://developer.github.com/v3/repos/keys/."
'''
EXAMPLES = '''
# add a new read-only deploy key to a GitHub repository using basic authentication
- github_deploy_key:
owner: "johndoe"
repo: "example"
name: "new-deploy-key"
key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAwXxn7kIMNWzcDfou..."
read_only: yes
username: "johndoe"
password: "supersecretpassword"
# remove an existing deploy key from a GitHub repository
- github_deploy_key:
owner: "johndoe"
repository: "example"
name: "new-deploy-key"
key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAwXxn7kIMNWzcDfou..."
force: yes
username: "johndoe"
password: "supersecretpassword"
state: absent
# add a new deploy key to a GitHub repository, replace an existing key, use an OAuth2 token to authenticate
- github_deploy_key:
owner: "johndoe"
repository: "example"
name: "new-deploy-key"
key: "{{ lookup('file', '~/.ssh/github.pub') }}"
force: yes
token: "ABAQDAwXxn7kIMNWzcDfo..."
# re-add a deploy key to a GitHub repository but with a different name
- github_deploy_key:
owner: "johndoe"
repository: "example"
name: "replace-deploy-key"
key: "{{ lookup('file', '~/.ssh/github.pub') }}"
username: "johndoe"
password: "supersecretpassword"
# add a new deploy key to a GitHub repository using 2FA
- github_deploy_key:
owner: "johndoe"
repo: "example"
name: "new-deploy-key-2"
key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAwXxn7kIMNWzcDfou..."
username: "johndoe"
password: "supersecretpassword"
otp: 123456
# add a read-only deploy key to a repository hosted on GitHub Enterprise
- github_deploy_key:
github_url: "https://api.example.com"
owner: "janedoe"
repo: "example"
name: "new-deploy-key"
key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAwXxn7kIMNWzcDfou..."
read_only: yes
username: "janedoe"
password: "supersecretpassword"
'''
RETURN = '''
msg:
description: the status message describing what occurred
returned: always
type: str
sample: "Deploy key added successfully"
http_status_code:
description: the HTTP status code returned by the GitHub API
returned: failed
type: int
sample: 400
error:
description: the error message returned by the GitHub API
returned: failed
type: str
sample: "key is already in use"
id:
description: the key identifier assigned by GitHub for the deploy key
returned: changed
type: int
sample: 24381901
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
from re import findall
class GithubDeployKey(object):
def __init__(self, module):
self.module = module
self.github_url = self.module.params['github_url']
self.name = module.params['name']
self.key = module.params['key']
self.state = module.params['state']
self.read_only = module.params.get('read_only', True)
self.force = module.params.get('force', False)
self.username = module.params.get('username', None)
self.password = module.params.get('password', None)
self.token = module.params.get('token', None)
self.otp = module.params.get('otp', None)
@property
def url(self):
owner = self.module.params['owner']
repo = self.module.params['repo']
return "{0}/repos/{1}/{2}/keys".format(self.github_url, owner, repo)
@property
def headers(self):
if self.username is not None and self.password is not None:
self.module.params['url_username'] = self.username
self.module.params['url_password'] = self.password
self.module.params['force_basic_auth'] = True
if self.otp is not None:
return {"X-GitHub-OTP": self.otp}
elif self.token is not None:
return {"Authorization": "token {0}".format(self.token)}
else:
return None
def paginate(self, url):
while url:
resp, info = fetch_url(self.module, url, headers=self.headers, method="GET")
if info["status"] == 200:
yield self.module.from_json(resp.read())
links = {}
for x, y in findall(r'<([^>]+)>;\s*rel="(\w+)"', info["link"]):
links[y] = x
url = links.get('next')
else:
self.handle_error(method="GET", info=info)
def get_existing_key(self):
for keys in self.paginate(self.url):
if keys:
for i in keys:
existing_key_id = str(i["id"])
if i["key"].split() == self.key.split()[:2]:
return existing_key_id
elif i['title'] == self.name and self.force:
return existing_key_id
else:
return None
def add_new_key(self):
request_body = {"title": self.name, "key": self.key, "read_only": self.read_only}
resp, info = fetch_url(self.module, self.url, data=self.module.jsonify(request_body), headers=self.headers, method="POST", timeout=30)
status_code = info["status"]
if status_code == 201:
response_body = self.module.from_json(resp.read())
key_id = response_body["id"]
self.module.exit_json(changed=True, msg="Deploy key successfully added", id=key_id)
elif status_code == 422:
self.module.exit_json(changed=False, msg="Deploy key already exists")
else:
self.handle_error(method="POST", info=info)
def remove_existing_key(self, key_id):
resp, info = fetch_url(self.module, "{0}/{1}".format(self.url, key_id), headers=self.headers, method="DELETE")
status_code = info["status"]
if status_code == 204:
if self.state == 'absent':
self.module.exit_json(changed=True, msg="Deploy key successfully deleted", id=key_id)
else:
self.handle_error(method="DELETE", info=info, key_id=key_id)
def handle_error(self, method, info, key_id=None):
status_code = info['status']
body = info.get('body')
if body:
err = self.module.from_json(body)['message']
if status_code == 401:
self.module.fail_json(msg="Failed to connect to {0} due to invalid credentials".format(self.github_url), http_status_code=status_code, error=err)
elif status_code == 404:
self.module.fail_json(msg="GitHub repository does not exist", http_status_code=status_code, error=err)
else:
if method == "GET":
self.module.fail_json(msg="Failed to retrieve existing deploy keys", http_status_code=status_code, error=err)
elif method == "POST":
self.module.fail_json(msg="Failed to add deploy key", http_status_code=status_code, error=err)
elif method == "DELETE":
self.module.fail_json(msg="Failed to delete existing deploy key", id=key_id, http_status_code=status_code, error=err)
def main():
module = AnsibleModule(
argument_spec=dict(
github_url=dict(required=False, type='str', default="https://api.github.com"),
owner=dict(required=True, type='str', aliases=['account', 'organization']),
repo=dict(required=True, type='str', aliases=['repository']),
name=dict(required=True, type='str', aliases=['title', 'label']),
key=dict(required=True, type='str'),
read_only=dict(required=False, type='bool', default=True),
state=dict(default='present', choices=['present', 'absent']),
force=dict(required=False, type='bool', default=False),
username=dict(required=False, type='str'),
password=dict(required=False, type='str', no_log=True),
otp=dict(required=False, type='int', aliases=['2fa_token'], no_log=True),
token=dict(required=False, type='str', no_log=True)
),
mutually_exclusive=[
['password', 'token']
],
required_together=[
['username', 'password'],
['otp', 'username', 'password']
],
required_one_of=[
['username', 'token']
],
supports_check_mode=True,
)
deploy_key = GithubDeployKey(module)
if module.check_mode:
key_id = deploy_key.get_existing_key()
if deploy_key.state == "present" and key_id is None:
module.exit_json(changed=True)
elif deploy_key.state == "present" and key_id is not None:
module.exit_json(changed=False)
# to forcefully modify an existing key, the existing key must be deleted first
if deploy_key.state == 'absent' or deploy_key.force:
key_id = deploy_key.get_existing_key()
if key_id is not None:
deploy_key.remove_existing_key(key_id)
elif deploy_key.state == 'absent':
module.exit_json(changed=False, msg="Deploy key does not exist")
if deploy_key.state == "present":
deploy_key.add_new_key()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,197 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2013, Phillip Gentry <phillip@cx.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['deprecated'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: github_hooks
short_description: Manages GitHub service hooks.
deprecated:
removed_in: "2.12"
why: Replaced by more granular modules
alternative: Use M(github_webhook) and M(github_webhook_info) instead.
description:
- Adds service hooks and removes service hooks that have an error status.
options:
user:
description:
- GitHub username.
required: true
oauthkey:
description:
- The oauth key provided by GitHub. It can be found/generated on GitHub under "Edit Your Profile" >> "Developer settings" >> "Personal Access Tokens"
required: true
repo:
description:
- >
This is the API url for the repository you want to manage hooks for. It should be in the form of: https://api.github.com/repos/user:/repo:.
Note this is different than the normal repo url.
required: true
hookurl:
description:
- When creating a new hook, this is the url that you want GitHub to post to. It is only required when creating a new hook.
required: false
action:
description:
- This tells the githooks module what you want it to do.
required: true
choices: [ "create", "cleanall", "list", "clean504" ]
validate_certs:
description:
- If C(no), SSL certificates for the target repo will not be validated. This should only be used
on personally controlled sites using self-signed certificates.
required: false
default: 'yes'
type: bool
content_type:
description:
- Content type to use for requests made to the webhook
required: false
default: 'json'
choices: ['json', 'form']
author: "Phillip Gentry, CX Inc (@pcgentry)"
'''
EXAMPLES = '''
# Example creating a new service hook. It ignores duplicates.
- github_hooks:
action: create
hookurl: http://11.111.111.111:2222
user: '{{ gituser }}'
oauthkey: '{{ oauthkey }}'
repo: https://api.github.com/repos/pcgentry/Github-Auto-Deploy
# Cleaning all hooks for this repo that had an error on the last update. Since this works for all hooks in a repo it is probably best that this would
# be called from a handler.
- github_hooks:
action: cleanall
user: '{{ gituser }}'
oauthkey: '{{ oauthkey }}'
repo: '{{ repo }}'
delegate_to: localhost
'''
import json
import base64
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
from ansible.module_utils._text import to_bytes
def request(module, url, user, oauthkey, data='', method='GET'):
auth = base64.b64encode(to_bytes('%s:%s' % (user, oauthkey)).replace('\n', ''))
headers = {
'Authorization': 'Basic %s' % auth,
}
response, info = fetch_url(module, url, headers=headers, data=data, method=method)
return response, info
def _list(module, oauthkey, repo, user):
url = "%s/hooks" % repo
response, info = request(module, url, user, oauthkey)
if info['status'] != 200:
return False, ''
else:
return False, response.read()
def _clean504(module, oauthkey, repo, user):
current_hooks = _list(module, oauthkey, repo, user)[1]
decoded = json.loads(current_hooks)
for hook in decoded:
if hook['last_response']['code'] == 504:
_delete(module, oauthkey, repo, user, hook['id'])
return 0, current_hooks
def _cleanall(module, oauthkey, repo, user):
current_hooks = _list(module, oauthkey, repo, user)[1]
decoded = json.loads(current_hooks)
for hook in decoded:
if hook['last_response']['code'] != 200:
_delete(module, oauthkey, repo, user, hook['id'])
return 0, current_hooks
def _create(module, hookurl, oauthkey, repo, user, content_type):
url = "%s/hooks" % repo
values = {
"active": True,
"name": "web",
"config": {
"url": "%s" % hookurl,
"content_type": "%s" % content_type
}
}
data = json.dumps(values)
response, info = request(module, url, user, oauthkey, data=data, method='POST')
if info['status'] != 200:
return 0, '[]'
else:
return 0, response.read()
def _delete(module, oauthkey, repo, user, hookid):
url = "%s/hooks/%s" % (repo, hookid)
response, info = request(module, url, user, oauthkey, method='DELETE')
return response.read()
def main():
module = AnsibleModule(
argument_spec=dict(
action=dict(required=True, choices=['list', 'clean504', 'cleanall', 'create']),
hookurl=dict(required=False),
oauthkey=dict(required=True, no_log=True),
repo=dict(required=True),
user=dict(required=True),
validate_certs=dict(default='yes', type='bool'),
content_type=dict(default='json', choices=['json', 'form']),
)
)
action = module.params['action']
hookurl = module.params['hookurl']
oauthkey = module.params['oauthkey']
repo = module.params['repo']
user = module.params['user']
content_type = module.params['content_type']
if action == "list":
(rc, out) = _list(module, oauthkey, repo, user)
if action == "clean504":
(rc, out) = _clean504(module, oauthkey, repo, user)
if action == "cleanall":
(rc, out) = _cleanall(module, oauthkey, repo, user)
if action == "create":
(rc, out) = _create(module, hookurl, oauthkey, repo, user, content_type)
if rc != 0:
module.fail_json(msg="failed", result=out)
module.exit_json(msg="success", result=out)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,118 @@
#!/usr/bin/python
# Copyright: (c) 2017-18, Abhijeet Kasurde <akasurde@redhat.com>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
module: github_issue
short_description: View GitHub issue.
description:
- View GitHub issue for a given repository and organization.
options:
repo:
description:
- Name of repository from which issue needs to be retrieved.
required: true
organization:
description:
- Name of the GitHub organization in which the repository is hosted.
required: true
issue:
description:
- Issue number for which information is required.
required: true
action:
description:
- Get various details about issue depending upon action specified.
default: 'get_status'
choices:
- 'get_status'
author:
- Abhijeet Kasurde (@Akasurde)
'''
RETURN = '''
get_status:
description: State of the GitHub issue
type: str
returned: success
sample: open, closed
'''
EXAMPLES = '''
- name: Check if GitHub issue is closed or not
github_issue:
organization: ansible
repo: ansible
issue: 23642
action: get_status
register: r
- name: Take action depending upon issue status
debug:
msg: Do something when issue 23642 is open
when: r.issue_status == 'open'
'''
import json
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
def main():
module = AnsibleModule(
argument_spec=dict(
organization=dict(required=True),
repo=dict(required=True),
issue=dict(type='int', required=True),
action=dict(choices=['get_status'], default='get_status'),
),
supports_check_mode=True,
)
organization = module.params['organization']
repo = module.params['repo']
issue = module.params['issue']
action = module.params['action']
result = dict()
headers = {
'Content-Type': 'application/json',
'Accept': 'application/vnd.github.v3+json',
}
url = "https://api.github.com/repos/%s/%s/issues/%s" % (organization, repo, issue)
response, info = fetch_url(module, url, headers=headers)
if not (200 <= info['status'] < 400):
if info['status'] == 404:
module.fail_json(msg="Failed to find issue %s" % issue)
module.fail_json(msg="Failed to send request to %s: %s" % (url, info['msg']))
gh_obj = json.loads(response.read())
if action == 'get_status' or action is None:
if module.check_mode:
result.update(changed=True)
else:
result.update(changed=True, issue_status=gh_obj['state'])
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,242 @@
#!/usr/bin/python
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
module: github_key
short_description: Manage GitHub access keys.
description:
- Creates, removes, or updates GitHub access keys.
options:
token:
description:
- GitHub Access Token with permission to list and create public keys.
required: true
name:
description:
- SSH key name
required: true
pubkey:
description:
- SSH public key value. Required when C(state=present).
state:
description:
- Whether to remove a key, ensure that it exists, or update its value.
choices: ['present', 'absent']
default: 'present'
force:
description:
- The default is C(yes), which will replace the existing remote key
if it's different than C(pubkey). If C(no), the key will only be
set if no key with the given C(name) exists.
type: bool
default: 'yes'
author: Robert Estelle (@erydo)
'''
RETURN = '''
deleted_keys:
description: An array of key objects that were deleted. Only present on state=absent
type: list
returned: When state=absent
sample: [{'id': 0, 'key': 'BASE64 encoded key', 'url': 'http://example.com/github key', 'created_at': 'YYYY-MM-DDTHH:MM:SZ', 'read_only': False}]
matching_keys:
description: An array of keys matching the specified name. Only present on state=present
type: list
returned: When state=present
sample: [{'id': 0, 'key': 'BASE64 encoded key', 'url': 'http://example.com/github key', 'created_at': 'YYYY-MM-DDTHH:MM:SZ', 'read_only': False}]
key:
description: Metadata about the key just created. Only present on state=present
type: dict
returned: success
sample: {'id': 0, 'key': 'BASE64 encoded key', 'url': 'http://example.com/github key', 'created_at': 'YYYY-MM-DDTHH:MM:SZ', 'read_only': False}
'''
EXAMPLES = '''
- name: Read SSH public key to authorize
shell: cat /home/foo/.ssh/id_rsa.pub
register: ssh_pub_key
- name: Authorize key with GitHub
local_action:
module: github_key
name: Access Key for Some Machine
token: '{{ github_access_token }}'
pubkey: '{{ ssh_pub_key.stdout }}'
'''
import json
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url
API_BASE = 'https://api.github.com'
class GitHubResponse(object):
def __init__(self, response, info):
self.content = response.read()
self.info = info
def json(self):
return json.loads(self.content)
def links(self):
links = {}
if 'link' in self.info:
link_header = self.info['link']
matches = re.findall('<([^>]+)>; rel="([^"]+)"', link_header)
for url, rel in matches:
links[rel] = url
return links
class GitHubSession(object):
def __init__(self, module, token):
self.module = module
self.token = token
def request(self, method, url, data=None):
headers = {
'Authorization': 'token %s' % self.token,
'Content-Type': 'application/json',
'Accept': 'application/vnd.github.v3+json',
}
response, info = fetch_url(
self.module, url, method=method, data=data, headers=headers)
if not (200 <= info['status'] < 400):
self.module.fail_json(
msg=(" failed to send request %s to %s: %s"
% (method, url, info['msg'])))
return GitHubResponse(response, info)
def get_all_keys(session):
url = API_BASE + '/user/keys'
result = []
while url:
r = session.request('GET', url)
result.extend(r.json())
url = r.links().get('next')
return result
def create_key(session, name, pubkey, check_mode):
if check_mode:
from datetime import datetime
now = datetime.utcnow()
return {
'id': 0,
'key': pubkey,
'title': name,
'url': 'http://example.com/CHECK_MODE_GITHUB_KEY',
'created_at': datetime.strftime(now, '%Y-%m-%dT%H:%M:%SZ'),
'read_only': False,
'verified': False
}
else:
return session.request(
'POST',
API_BASE + '/user/keys',
data=json.dumps({'title': name, 'key': pubkey})).json()
def delete_keys(session, to_delete, check_mode):
if check_mode:
return
for key in to_delete:
session.request('DELETE', API_BASE + '/user/keys/%s' % key["id"])
def ensure_key_absent(session, name, check_mode):
to_delete = [key for key in get_all_keys(session) if key['title'] == name]
delete_keys(session, to_delete, check_mode=check_mode)
return {'changed': bool(to_delete),
'deleted_keys': to_delete}
def ensure_key_present(module, session, name, pubkey, force, check_mode):
all_keys = get_all_keys(session)
matching_keys = [k for k in all_keys if k['title'] == name]
deleted_keys = []
new_signature = pubkey.split(' ')[1]
for key in all_keys:
existing_signature = key['key'].split(' ')[1]
if new_signature == existing_signature and key['title'] != name:
module.fail_json(msg=(
"another key with the same content is already registered "
"under the name |{0}|").format(key['title']))
if matching_keys and force and matching_keys[0]['key'].split(' ')[1] != new_signature:
delete_keys(session, matching_keys, check_mode=check_mode)
(deleted_keys, matching_keys) = (matching_keys, [])
if not matching_keys:
key = create_key(session, name, pubkey, check_mode=check_mode)
else:
key = matching_keys[0]
return {
'changed': bool(deleted_keys or not matching_keys),
'deleted_keys': deleted_keys,
'matching_keys': matching_keys,
'key': key
}
def main():
argument_spec = {
'token': {'required': True, 'no_log': True},
'name': {'required': True},
'pubkey': {},
'state': {'choices': ['present', 'absent'], 'default': 'present'},
'force': {'default': True, 'type': 'bool'},
}
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
token = module.params['token']
name = module.params['name']
state = module.params['state']
force = module.params['force']
pubkey = module.params.get('pubkey')
if pubkey:
pubkey_parts = pubkey.split(' ')
# Keys consist of a protocol, the key data, and an optional comment.
if len(pubkey_parts) < 2:
module.fail_json(msg='"pubkey" parameter has an invalid format')
elif state == 'present':
module.fail_json(msg='"pubkey" is required when state=present')
session = GitHubSession(module, token)
if state == 'present':
result = ensure_key_present(module, session, name, pubkey, force=force,
check_mode=module.check_mode)
elif state == 'absent':
result = ensure_key_absent(session, name, check_mode=module.check_mode)
module.exit_json(**result)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,218 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright: Ansible Team
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: github_release
short_description: Interact with GitHub Releases
description:
- Fetch metadata about GitHub Releases
options:
token:
description:
- GitHub Personal Access Token for authenticating. Mutually exclusive with C(password).
user:
description:
- The GitHub account that owns the repository
required: true
password:
description:
- The GitHub account password for the user. Mutually exclusive with C(token).
repo:
description:
- Repository name
required: true
action:
description:
- Action to perform
required: true
choices: [ 'latest_release', 'create_release' ]
tag:
description:
- Tag name when creating a release. Required when using action is set to C(create_release).
target:
description:
- Target of release when creating a release
name:
description:
- Name of release when creating a release
body:
description:
- Description of the release when creating a release
draft:
description:
- Sets if the release is a draft or not. (boolean)
type: 'bool'
default: 'no'
prerelease:
description:
- Sets if the release is a prerelease or not. (boolean)
type: bool
default: 'no'
author:
- "Adrian Moisey (@adrianmoisey)"
requirements:
- "github3.py >= 1.0.0a3"
'''
EXAMPLES = '''
- name: Get latest release of a public repository
github_release:
user: ansible
repo: ansible
action: latest_release
- name: Get latest release of testuseer/testrepo
github_release:
token: tokenabc1234567890
user: testuser
repo: testrepo
action: latest_release
- name: Get latest release of test repo using username and password. Ansible 2.4.
github_release:
user: testuser
password: secret123
repo: testrepo
action: latest_release
- name: Create a new release
github_release:
token: tokenabc1234567890
user: testuser
repo: testrepo
action: create_release
tag: test
target: master
name: My Release
body: Some description
'''
RETURN = '''
create_release:
description:
- Version of the created release
- "For Ansible version 2.5 and later, if specified release version already exists, then State is unchanged"
- "For Ansible versions prior to 2.5, if specified release version already exists, then State is skipped"
type: str
returned: success
sample: 1.1.0
latest_release:
description: Version of the latest release
type: str
returned: success
sample: 1.1.0
'''
import traceback
GITHUB_IMP_ERR = None
try:
import github3
HAS_GITHUB_API = True
except ImportError:
GITHUB_IMP_ERR = traceback.format_exc()
HAS_GITHUB_API = False
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native
def main():
module = AnsibleModule(
argument_spec=dict(
repo=dict(required=True),
user=dict(required=True),
password=dict(no_log=True),
token=dict(no_log=True),
action=dict(
required=True, choices=['latest_release', 'create_release']),
tag=dict(type='str'),
target=dict(type='str'),
name=dict(type='str'),
body=dict(type='str'),
draft=dict(type='bool', default=False),
prerelease=dict(type='bool', default=False),
),
supports_check_mode=True,
mutually_exclusive=(('password', 'token'),),
required_if=[('action', 'create_release', ['tag']),
('action', 'create_release', ['password', 'token'], True)],
)
if not HAS_GITHUB_API:
module.fail_json(msg=missing_required_lib('github3.py >= 1.0.0a3'),
exception=GITHUB_IMP_ERR)
repo = module.params['repo']
user = module.params['user']
password = module.params['password']
login_token = module.params['token']
action = module.params['action']
tag = module.params.get('tag')
target = module.params.get('target')
name = module.params.get('name')
body = module.params.get('body')
draft = module.params.get('draft')
prerelease = module.params.get('prerelease')
# login to github
try:
if password:
gh_obj = github3.login(user, password=password)
elif login_token:
gh_obj = github3.login(token=login_token)
else:
gh_obj = github3.GitHub()
# test if we're actually logged in
if password or login_token:
gh_obj.me()
except github3.exceptions.AuthenticationFailed as e:
module.fail_json(msg='Failed to connect to GitHub: %s' % to_native(e),
details="Please check username and password or token "
"for repository %s" % repo)
repository = gh_obj.repository(user, repo)
if not repository:
module.fail_json(msg="Repository %s/%s doesn't exist" % (user, repo))
if action == 'latest_release':
release = repository.latest_release()
if release:
module.exit_json(tag=release.tag_name)
else:
module.exit_json(tag=None)
if action == 'create_release':
release_exists = repository.release_from_tag(tag)
if release_exists:
module.exit_json(changed=False, msg="Release for tag %s already exists." % tag)
release = repository.create_release(
tag, target, name, body, draft, prerelease)
if release:
module.exit_json(changed=True, tag=release.tag_name)
else:
module.exit_json(changed=False, tag=None)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,284 @@
#!/usr/bin/python
#
# Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: github_webhook
short_description: Manage GitHub webhooks
description:
- "Create and delete GitHub webhooks"
requirements:
- "PyGithub >= 1.3.5"
options:
repository:
description:
- Full name of the repository to configure a hook for
required: true
aliases:
- repo
url:
description:
- URL to which payloads will be delivered
required: true
content_type:
description:
- The media type used to serialize the payloads
required: false
choices: [ form, json ]
default: form
secret:
description:
- The shared secret between GitHub and the payload URL.
required: false
insecure_ssl:
description:
- >
Flag to indicate that GitHub should skip SSL verification when calling
the hook.
required: false
type: bool
default: false
events:
description:
- >
A list of GitHub events the hook is triggered for. Events are listed at
U(https://developer.github.com/v3/activity/events/types/). Required
unless C(state) is C(absent)
required: false
active:
description:
- Whether or not the hook is active
required: false
type: bool
default: true
state:
description:
- Whether the hook should be present or absent
required: false
choices: [ absent, present ]
default: present
user:
description:
- User to authenticate to GitHub as
required: true
password:
description:
- Password to authenticate to GitHub with
required: false
token:
description:
- Token to authenticate to GitHub with
required: false
github_url:
description:
- Base URL of the GitHub API
required: false
default: https://api.github.com
author:
- "Chris St. Pierre (@stpierre)"
'''
EXAMPLES = '''
- name: create a new webhook that triggers on push (password auth)
github_webhook:
repository: ansible/ansible
url: https://www.example.com/hooks/
events:
- push
user: "{{ github_user }}"
password: "{{ github_password }}"
- name: create a new webhook in a github enterprise installation with multiple event triggers (token auth)
github_webhook:
repository: myorg/myrepo
url: https://jenkins.example.com/ghprbhook/
content_type: json
secret: "{{ github_shared_secret }}"
insecure_ssl: True
events:
- issue_comment
- pull_request
user: "{{ github_user }}"
token: "{{ github_user_api_token }}"
github_url: https://github.example.com
- name: delete a webhook (password auth)
github_webhook:
repository: ansible/ansible
url: https://www.example.com/hooks/
state: absent
user: "{{ github_user }}"
password: "{{ github_password }}"
'''
RETURN = '''
---
hook_id:
description: The GitHub ID of the hook created/updated
returned: when state is 'present'
type: int
sample: 6206
'''
import traceback
GITHUB_IMP_ERR = None
try:
import github
HAS_GITHUB = True
except ImportError:
GITHUB_IMP_ERR = traceback.format_exc()
HAS_GITHUB = False
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native
def _create_hook_config(module):
return {
"url": module.params["url"],
"content_type": module.params["content_type"],
"secret": module.params.get("secret"),
"insecure_ssl": "1" if module.params["insecure_ssl"] else "0"
}
def create_hook(repo, module):
config = _create_hook_config(module)
try:
hook = repo.create_hook(
name="web",
config=config,
events=module.params["events"],
active=module.params["active"])
except github.GithubException as err:
module.fail_json(msg="Unable to create hook for repository %s: %s" % (
repo.full_name, to_native(err)))
data = {"hook_id": hook.id}
return True, data
def update_hook(repo, hook, module):
config = _create_hook_config(module)
try:
hook.update()
hook.edit(
name="web",
config=config,
events=module.params["events"],
active=module.params["active"])
changed = hook.update()
except github.GithubException as err:
module.fail_json(msg="Unable to modify hook for repository %s: %s" % (
repo.full_name, to_native(err)))
data = {"hook_id": hook.id}
return changed, data
def main():
module = AnsibleModule(
argument_spec=dict(
repository=dict(type='str', required=True, aliases=['repo']),
url=dict(type='str', required=True),
content_type=dict(
type='str',
choices=('json', 'form'),
required=False,
default='form'),
secret=dict(type='str', required=False, no_log=True),
insecure_ssl=dict(type='bool', required=False, default=False),
events=dict(type='list', elements='str', required=False),
active=dict(type='bool', required=False, default=True),
state=dict(
type='str',
required=False,
choices=('absent', 'present'),
default='present'),
user=dict(type='str', required=True),
password=dict(type='str', required=False, no_log=True),
token=dict(type='str', required=False, no_log=True),
github_url=dict(
type='str', required=False, default="https://api.github.com")),
mutually_exclusive=(('password', 'token'),),
required_one_of=(("password", "token"),),
required_if=(("state", "present", ("events",)),),
)
if not HAS_GITHUB:
module.fail_json(msg=missing_required_lib('PyGithub'),
exception=GITHUB_IMP_ERR)
try:
github_conn = github.Github(
module.params["user"],
module.params.get("password") or module.params.get("token"),
base_url=module.params["github_url"])
except github.GithubException as err:
module.fail_json(msg="Could not connect to GitHub at %s: %s" % (
module.params["github_url"], to_native(err)))
try:
repo = github_conn.get_repo(module.params["repository"])
except github.BadCredentialsException as err:
module.fail_json(msg="Could not authenticate to GitHub at %s: %s" % (
module.params["github_url"], to_native(err)))
except github.UnknownObjectException as err:
module.fail_json(
msg="Could not find repository %s in GitHub at %s: %s" % (
module.params["repository"], module.params["github_url"],
to_native(err)))
except Exception as err:
module.fail_json(
msg="Could not fetch repository %s from GitHub at %s: %s" %
(module.params["repository"], module.params["github_url"],
to_native(err)),
exception=traceback.format_exc())
hook = None
try:
for hook in repo.get_hooks():
if hook.config.get("url") == module.params["url"]:
break
else:
hook = None
except github.GithubException as err:
module.fail_json(msg="Unable to get hooks from repository %s: %s" % (
module.params["repository"], to_native(err)))
changed = False
data = {}
if hook is None and module.params["state"] == "present":
changed, data = create_hook(repo, module)
elif hook is not None and module.params["state"] == "absent":
try:
hook.delete()
except github.GithubException as err:
module.fail_json(
msg="Unable to delete hook from repository %s: %s" % (
repo.full_name, to_native(err)))
else:
changed = True
elif hook is not None and module.params["state"] == "present":
changed, data = update_hook(repo, hook, module)
# else, there is no hook and we want there to be no hook
module.exit_json(changed=changed, **data)
if __name__ == '__main__':
main()

View file

@ -0,0 +1 @@
github_webhook_info.py

View file

@ -0,0 +1,174 @@
#!/usr/bin/python
#
# Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: github_webhook_info
short_description: Query information about GitHub webhooks
description:
- "Query information about GitHub webhooks"
- This module was called C(github_webhook_facts) before Ansible 2.9. The usage did not change.
requirements:
- "PyGithub >= 1.3.5"
options:
repository:
description:
- Full name of the repository to configure a hook for
required: true
aliases:
- repo
user:
description:
- User to authenticate to GitHub as
required: true
password:
description:
- Password to authenticate to GitHub with
required: false
token:
description:
- Token to authenticate to GitHub with
required: false
github_url:
description:
- Base URL of the github api
required: false
default: https://api.github.com
author:
- "Chris St. Pierre (@stpierre)"
'''
EXAMPLES = '''
- name: list hooks for a repository (password auth)
github_webhook_info:
repository: ansible/ansible
user: "{{ github_user }}"
password: "{{ github_password }}"
register: ansible_webhooks
- name: list hooks for a repository on GitHub Enterprise (token auth)
github_webhook_info:
repository: myorg/myrepo
user: "{{ github_user }}"
token: "{{ github_user_api_token }}"
github_url: https://github.example.com/api/v3/
register: myrepo_webhooks
'''
RETURN = '''
---
hooks:
description: A list of hooks that exist for the repo
returned: always
type: list
sample: >
[{"has_shared_secret": true,
"url": "https://jenkins.example.com/ghprbhook/",
"events": ["issue_comment", "pull_request"],
"insecure_ssl": "1",
"content_type": "json",
"active": true,
"id": 6206,
"last_response": {"status": "active", "message": "OK", "code": 200}}]
'''
import traceback
GITHUB_IMP_ERR = None
try:
import github
HAS_GITHUB = True
except ImportError:
GITHUB_IMP_ERR = traceback.format_exc()
HAS_GITHUB = False
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native
def _munge_hook(hook_obj):
retval = {
"active": hook_obj.active,
"events": hook_obj.events,
"id": hook_obj.id,
"url": hook_obj.url,
}
retval.update(hook_obj.config)
retval["has_shared_secret"] = "secret" in retval
if "secret" in retval:
del retval["secret"]
retval["last_response"] = hook_obj.last_response.raw_data
return retval
def main():
module = AnsibleModule(
argument_spec=dict(
repository=dict(type='str', required=True, aliases=["repo"]),
user=dict(type='str', required=True),
password=dict(type='str', required=False, no_log=True),
token=dict(type='str', required=False, no_log=True),
github_url=dict(
type='str', required=False, default="https://api.github.com")),
mutually_exclusive=(('password', 'token'), ),
required_one_of=(("password", "token"), ),
supports_check_mode=True)
if module._name == 'github_webhook_facts':
module.deprecate("The 'github_webhook_facts' module has been renamed to 'github_webhook_info'", version='2.13')
if not HAS_GITHUB:
module.fail_json(msg=missing_required_lib('PyGithub'),
exception=GITHUB_IMP_ERR)
try:
github_conn = github.Github(
module.params["user"],
module.params.get("password") or module.params.get("token"),
base_url=module.params["github_url"])
except github.GithubException as err:
module.fail_json(msg="Could not connect to GitHub at %s: %s" % (
module.params["github_url"], to_native(err)))
try:
repo = github_conn.get_repo(module.params["repository"])
except github.BadCredentialsException as err:
module.fail_json(msg="Could not authenticate to GitHub at %s: %s" % (
module.params["github_url"], to_native(err)))
except github.UnknownObjectException as err:
module.fail_json(
msg="Could not find repository %s in GitHub at %s: %s" % (
module.params["repository"], module.params["github_url"],
to_native(err)))
except Exception as err:
module.fail_json(
msg="Could not fetch repository %s from GitHub at %s: %s" %
(module.params["repository"], module.params["github_url"],
to_native(err)),
exception=traceback.format_exc())
try:
hooks = [_munge_hook(h) for h in repo.get_hooks()]
except github.GithubException as err:
module.fail_json(
msg="Unable to get hooks from repository %s: %s" %
(module.params["repository"], to_native(err)),
exception=traceback.format_exc())
module.exit_json(changed=False, hooks=hooks)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,300 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Guillaume Martinez (lunik@tiwabbit.fr)
# Copyright: (c) 2018, Marcus Watkins <marwatk@marcuswatkins.net>
# Based on code:
# Copyright: (c) 2013, Phillip Gentry <phillip@cx.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
module: gitlab_deploy_key
short_description: Manages GitLab project deploy keys.
description:
- Adds, updates and removes project deploy keys
author:
- Marcus Watkins (@marwatk)
- Guillaume Martinez (@Lunik)
requirements:
- python >= 2.7
- python-gitlab python module
extends_documentation_fragment:
- community.general.auth_basic
options:
api_token:
description:
- GitLab token for logging in.
type: str
project:
description:
- Id or Full path of project in the form of group/name.
required: true
type: str
title:
description:
- Deploy key's title.
required: true
type: str
key:
description:
- Deploy key
required: true
type: str
can_push:
description:
- Whether this key can push to the project.
type: bool
default: no
state:
description:
- When C(present) the deploy key added to the project if it doesn't exist.
- When C(absent) it will be removed from the project if it exists.
required: true
default: present
type: str
choices: [ "present", "absent" ]
'''
EXAMPLES = '''
- name: "Adding a project deploy key"
gitlab_deploy_key:
api_url: https://gitlab.example.com/
api_token: "{{ api_token }}"
project: "my_group/my_project"
title: "Jenkins CI"
state: present
key: "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9w..."
- name: "Update the above deploy key to add push access"
gitlab_deploy_key:
api_url: https://gitlab.example.com/
api_token: "{{ api_token }}"
project: "my_group/my_project"
title: "Jenkins CI"
state: present
can_push: yes
- name: "Remove the previous deploy key from the project"
gitlab_deploy_key:
api_url: https://gitlab.example.com/
api_token: "{{ api_token }}"
project: "my_group/my_project"
state: absent
key: "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9w..."
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Success"
result:
description: json parsed response from the server
returned: always
type: dict
error:
description: the error message returned by the GitLab API
returned: failed
type: str
sample: "400: key is already in use"
deploy_key:
description: API object
returned: always
type: dict
'''
import re
import traceback
GITLAB_IMP_ERR = None
try:
import gitlab
HAS_GITLAB_PACKAGE = True
except Exception:
GITLAB_IMP_ERR = traceback.format_exc()
HAS_GITLAB_PACKAGE = False
from ansible.module_utils.api import basic_auth_argument_spec
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native
from ansible_collections.community.general.plugins.module_utils.gitlab import findProject, gitlabAuthentication
class GitLabDeployKey(object):
def __init__(self, module, gitlab_instance):
self._module = module
self._gitlab = gitlab_instance
self.deployKeyObject = None
'''
@param project Project object
@param key_title Title of the key
@param key_key String of the key
@param key_can_push Option of the deployKey
@param options Deploy key options
'''
def createOrUpdateDeployKey(self, project, key_title, key_key, options):
changed = False
# Because we have already call existsDeployKey in main()
if self.deployKeyObject is None:
deployKey = self.createDeployKey(project, {
'title': key_title,
'key': key_key,
'can_push': options['can_push']})
changed = True
else:
changed, deployKey = self.updateDeployKey(self.deployKeyObject, {
'can_push': options['can_push']})
self.deployKeyObject = deployKey
if changed:
if self._module.check_mode:
self._module.exit_json(changed=True, msg="Successfully created or updated the deploy key %s" % key_title)
try:
deployKey.save()
except Exception as e:
self._module.fail_json(msg="Failed to update deploy key: %s " % e)
return True
else:
return False
'''
@param project Project Object
@param arguments Attributes of the deployKey
'''
def createDeployKey(self, project, arguments):
if self._module.check_mode:
return True
try:
deployKey = project.keys.create(arguments)
except (gitlab.exceptions.GitlabCreateError) as e:
self._module.fail_json(msg="Failed to create deploy key: %s " % to_native(e))
return deployKey
'''
@param deployKey Deploy Key Object
@param arguments Attributes of the deployKey
'''
def updateDeployKey(self, deployKey, arguments):
changed = False
for arg_key, arg_value in arguments.items():
if arguments[arg_key] is not None:
if getattr(deployKey, arg_key) != arguments[arg_key]:
setattr(deployKey, arg_key, arguments[arg_key])
changed = True
return (changed, deployKey)
'''
@param project Project object
@param key_title Title of the key
'''
def findDeployKey(self, project, key_title):
deployKeys = project.keys.list()
for deployKey in deployKeys:
if (deployKey.title == key_title):
return deployKey
'''
@param project Project object
@param key_title Title of the key
'''
def existsDeployKey(self, project, key_title):
# When project exists, object will be stored in self.projectObject.
deployKey = self.findDeployKey(project, key_title)
if deployKey:
self.deployKeyObject = deployKey
return True
return False
def deleteDeployKey(self):
if self._module.check_mode:
return True
return self.deployKeyObject.delete()
def main():
argument_spec = basic_auth_argument_spec()
argument_spec.update(dict(
api_token=dict(type='str', no_log=True),
state=dict(type='str', default="present", choices=["absent", "present"]),
project=dict(type='str', required=True),
key=dict(type='str', required=True),
can_push=dict(type='bool', default=False),
title=dict(type='str', required=True)
))
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=[
['api_username', 'api_token'],
['api_password', 'api_token']
],
required_together=[
['api_username', 'api_password']
],
required_one_of=[
['api_username', 'api_token']
],
supports_check_mode=True,
)
state = module.params['state']
project_identifier = module.params['project']
key_title = module.params['title']
key_keyfile = module.params['key']
key_can_push = module.params['can_push']
if not HAS_GITLAB_PACKAGE:
module.fail_json(msg=missing_required_lib("python-gitlab"), exception=GITLAB_IMP_ERR)
gitlab_instance = gitlabAuthentication(module)
gitlab_deploy_key = GitLabDeployKey(module, gitlab_instance)
project = findProject(gitlab_instance, project_identifier)
if project is None:
module.fail_json(msg="Failed to create deploy key: project %s doesn't exists" % project_identifier)
deployKey_exists = gitlab_deploy_key.existsDeployKey(project, key_title)
if state == 'absent':
if deployKey_exists:
gitlab_deploy_key.deleteDeployKey()
module.exit_json(changed=True, msg="Successfully deleted deploy key %s" % key_title)
else:
module.exit_json(changed=False, msg="Deploy key deleted or does not exists")
if state == 'present':
if gitlab_deploy_key.createOrUpdateDeployKey(project, key_title, key_keyfile, {'can_push': key_can_push}):
module.exit_json(changed=True, msg="Successfully created or updated the deploy key %s" % key_title,
deploy_key=gitlab_deploy_key.deployKeyObject._attrs)
else:
module.exit_json(changed=False, msg="No need to update the deploy key %s" % key_title,
deploy_key=gitlab_deploy_key.deployKeyObject._attrs)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,324 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Guillaume Martinez (lunik@tiwabbit.fr)
# Copyright: (c) 2015, Werner Dijkerman (ikben@werner-dijkerman.nl)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: gitlab_group
short_description: Creates/updates/deletes GitLab Groups
description:
- When the group does not exist in GitLab, it will be created.
- When the group does exist and state=absent, the group will be deleted.
author:
- Werner Dijkerman (@dj-wasabi)
- Guillaume Martinez (@Lunik)
requirements:
- python >= 2.7
- python-gitlab python module
extends_documentation_fragment:
- community.general.auth_basic
options:
api_token:
description:
- GitLab token for logging in.
type: str
name:
description:
- Name of the group you want to create.
required: true
type: str
path:
description:
- The path of the group you want to create, this will be api_url/group_path
- If not supplied, the group_name will be used.
type: str
description:
description:
- A description for the group.
type: str
state:
description:
- create or delete group.
- Possible values are present and absent.
default: present
type: str
choices: ["present", "absent"]
parent:
description:
- Allow to create subgroups
- Id or Full path of parent group in the form of group/name
type: str
visibility:
description:
- Default visibility of the group
choices: ["private", "internal", "public"]
default: private
type: str
'''
EXAMPLES = '''
- name: "Delete GitLab Group"
gitlab_group:
api_url: https://gitlab.example.com/
api_token: "{{ access_token }}"
validate_certs: False
name: my_first_group
state: absent
- name: "Create GitLab Group"
gitlab_group:
api_url: https://gitlab.example.com/
validate_certs: True
api_username: dj-wasabi
api_password: "MySecretPassword"
name: my_first_group
path: my_first_group
state: present
# The group will by created at https://gitlab.dj-wasabi.local/super_parent/parent/my_first_group
- name: "Create GitLab SubGroup"
gitlab_group:
api_url: https://gitlab.example.com/
validate_certs: True
api_username: dj-wasabi
api_password: "MySecretPassword"
name: my_first_group
path: my_first_group
state: present
parent: "super_parent/parent"
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Success"
result:
description: json parsed response from the server
returned: always
type: dict
error:
description: the error message returned by the GitLab API
returned: failed
type: str
sample: "400: path is already in use"
group:
description: API object
returned: always
type: dict
'''
import traceback
GITLAB_IMP_ERR = None
try:
import gitlab
HAS_GITLAB_PACKAGE = True
except Exception:
GITLAB_IMP_ERR = traceback.format_exc()
HAS_GITLAB_PACKAGE = False
from ansible.module_utils.api import basic_auth_argument_spec
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native
from ansible_collections.community.general.plugins.module_utils.gitlab import findGroup, gitlabAuthentication
class GitLabGroup(object):
def __init__(self, module, gitlab_instance):
self._module = module
self._gitlab = gitlab_instance
self.groupObject = None
'''
@param group Group object
'''
def getGroupId(self, group):
if group is not None:
return group.id
return None
'''
@param name Name of the group
@param parent Parent group full path
@param options Group options
'''
def createOrUpdateGroup(self, name, parent, options):
changed = False
# Because we have already call userExists in main()
if self.groupObject is None:
parent_id = self.getGroupId(parent)
group = self.createGroup({
'name': name,
'path': options['path'],
'parent_id': parent_id,
'visibility': options['visibility']})
changed = True
else:
changed, group = self.updateGroup(self.groupObject, {
'name': name,
'description': options['description'],
'visibility': options['visibility']})
self.groupObject = group
if changed:
if self._module.check_mode:
self._module.exit_json(changed=True, msg="Successfully created or updated the group %s" % name)
try:
group.save()
except Exception as e:
self._module.fail_json(msg="Failed to update group: %s " % e)
return True
else:
return False
'''
@param arguments Attributes of the group
'''
def createGroup(self, arguments):
if self._module.check_mode:
return True
try:
group = self._gitlab.groups.create(arguments)
except (gitlab.exceptions.GitlabCreateError) as e:
self._module.fail_json(msg="Failed to create group: %s " % to_native(e))
return group
'''
@param group Group Object
@param arguments Attributes of the group
'''
def updateGroup(self, group, arguments):
changed = False
for arg_key, arg_value in arguments.items():
if arguments[arg_key] is not None:
if getattr(group, arg_key) != arguments[arg_key]:
setattr(group, arg_key, arguments[arg_key])
changed = True
return (changed, group)
def deleteGroup(self):
group = self.groupObject
if len(group.projects.list()) >= 1:
self._module.fail_json(
msg="There are still projects in this group. These needs to be moved or deleted before this group can be removed.")
else:
if self._module.check_mode:
return True
try:
group.delete()
except Exception as e:
self._module.fail_json(msg="Failed to delete group: %s " % to_native(e))
'''
@param name Name of the groupe
@param full_path Complete path of the Group including parent group path. <parent_path>/<group_path>
'''
def existsGroup(self, project_identifier):
# When group/user exists, object will be stored in self.groupObject.
group = findGroup(self._gitlab, project_identifier)
if group:
self.groupObject = group
return True
return False
def main():
argument_spec = basic_auth_argument_spec()
argument_spec.update(dict(
api_token=dict(type='str', no_log=True),
name=dict(type='str', required=True),
path=dict(type='str'),
description=dict(type='str'),
state=dict(type='str', default="present", choices=["absent", "present"]),
parent=dict(type='str'),
visibility=dict(type='str', default="private", choices=["internal", "private", "public"]),
))
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=[
['api_username', 'api_token'],
['api_password', 'api_token'],
],
required_together=[
['api_username', 'api_password'],
],
required_one_of=[
['api_username', 'api_token']
],
supports_check_mode=True,
)
group_name = module.params['name']
group_path = module.params['path']
description = module.params['description']
state = module.params['state']
parent_identifier = module.params['parent']
group_visibility = module.params['visibility']
if not HAS_GITLAB_PACKAGE:
module.fail_json(msg=missing_required_lib("python-gitlab"), exception=GITLAB_IMP_ERR)
gitlab_instance = gitlabAuthentication(module)
# Define default group_path based on group_name
if group_path is None:
group_path = group_name.replace(" ", "_")
gitlab_group = GitLabGroup(module, gitlab_instance)
parent_group = None
if parent_identifier:
parent_group = findGroup(gitlab_instance, parent_identifier)
if not parent_group:
module.fail_json(msg="Failed create GitLab group: Parent group doesn't exists")
group_exists = gitlab_group.existsGroup(parent_group.full_path + '/' + group_path)
else:
group_exists = gitlab_group.existsGroup(group_path)
if state == 'absent':
if group_exists:
gitlab_group.deleteGroup()
module.exit_json(changed=True, msg="Successfully deleted group %s" % group_name)
else:
module.exit_json(changed=False, msg="Group deleted or does not exists")
if state == 'present':
if gitlab_group.createOrUpdateGroup(group_name, parent_group, {
"path": group_path,
"description": description,
"visibility": group_visibility}):
module.exit_json(changed=True, msg="Successfully created or updated the group %s" % group_name, group=gitlab_group.groupObject._attrs)
else:
module.exit_json(changed=False, msg="No need to update the group %s" % group_name, group=gitlab_group.groupObject._attrs)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,391 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Guillaume Martinez (lunik@tiwabbit.fr)
# Copyright: (c) 2018, Marcus Watkins <marwatk@marcuswatkins.net>
# Based on code:
# Copyright: (c) 2013, Phillip Gentry <phillip@cx.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: gitlab_hook
short_description: Manages GitLab project hooks.
description:
- Adds, updates and removes project hook
author:
- Marcus Watkins (@marwatk)
- Guillaume Martinez (@Lunik)
requirements:
- python >= 2.7
- python-gitlab python module
extends_documentation_fragment:
- community.general.auth_basic
options:
api_token:
description:
- GitLab token for logging in.
type: str
project:
description:
- Id or Full path of the project in the form of group/name.
required: true
type: str
hook_url:
description:
- The url that you want GitLab to post to, this is used as the primary key for updates and deletion.
required: true
type: str
state:
description:
- When C(present) the hook will be updated to match the input or created if it doesn't exist.
- When C(absent) hook will be deleted if it exists.
required: true
default: present
type: str
choices: [ "present", "absent" ]
push_events:
description:
- Trigger hook on push events.
type: bool
default: yes
push_events_branch_filter:
description:
- Branch name of wildcard to trigger hook on push events
type: str
issues_events:
description:
- Trigger hook on issues events.
type: bool
default: no
merge_requests_events:
description:
- Trigger hook on merge requests events.
type: bool
default: no
tag_push_events:
description:
- Trigger hook on tag push events.
type: bool
default: no
note_events:
description:
- Trigger hook on note events or when someone adds a comment.
type: bool
default: no
job_events:
description:
- Trigger hook on job events.
type: bool
default: no
pipeline_events:
description:
- Trigger hook on pipeline events.
type: bool
default: no
wiki_page_events:
description:
- Trigger hook on wiki events.
type: bool
default: no
hook_validate_certs:
description:
- Whether GitLab will do SSL verification when triggering the hook.
type: bool
default: no
aliases: [ enable_ssl_verification ]
token:
description:
- Secret token to validate hook messages at the receiver.
- If this is present it will always result in a change as it cannot be retrieved from GitLab.
- Will show up in the X-GitLab-Token HTTP request header.
required: false
type: str
'''
EXAMPLES = '''
- name: "Adding a project hook"
gitlab_hook:
api_url: https://gitlab.example.com/
api_token: "{{ access_token }}"
project: "my_group/my_project"
hook_url: "https://my-ci-server.example.com/gitlab-hook"
state: present
push_events: yes
tag_push_events: yes
hook_validate_certs: no
token: "my-super-secret-token-that-my-ci-server-will-check"
- name: "Delete the previous hook"
gitlab_hook:
api_url: https://gitlab.example.com/
api_token: "{{ access_token }}"
project: "my_group/my_project"
hook_url: "https://my-ci-server.example.com/gitlab-hook"
state: absent
- name: "Delete a hook by numeric project id"
gitlab_hook:
api_url: https://gitlab.example.com/
api_token: "{{ access_token }}"
project: 10
hook_url: "https://my-ci-server.example.com/gitlab-hook"
state: absent
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Success"
result:
description: json parsed response from the server
returned: always
type: dict
error:
description: the error message returned by the GitLab API
returned: failed
type: str
sample: "400: path is already in use"
hook:
description: API object
returned: always
type: dict
'''
import re
import traceback
GITLAB_IMP_ERR = None
try:
import gitlab
HAS_GITLAB_PACKAGE = True
except Exception:
GITLAB_IMP_ERR = traceback.format_exc()
HAS_GITLAB_PACKAGE = False
from ansible.module_utils.api import basic_auth_argument_spec
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native
from ansible_collections.community.general.plugins.module_utils.gitlab import findProject, gitlabAuthentication
class GitLabHook(object):
def __init__(self, module, gitlab_instance):
self._module = module
self._gitlab = gitlab_instance
self.hookObject = None
'''
@param project Project Object
@param hook_url Url to call on event
@param description Description of the group
@param parent Parent group full path
'''
def createOrUpdateHook(self, project, hook_url, options):
changed = False
# Because we have already call userExists in main()
if self.hookObject is None:
hook = self.createHook(project, {
'url': hook_url,
'push_events': options['push_events'],
'push_events_branch_filter': options['push_events_branch_filter'],
'issues_events': options['issues_events'],
'merge_requests_events': options['merge_requests_events'],
'tag_push_events': options['tag_push_events'],
'note_events': options['note_events'],
'job_events': options['job_events'],
'pipeline_events': options['pipeline_events'],
'wiki_page_events': options['wiki_page_events'],
'enable_ssl_verification': options['enable_ssl_verification'],
'token': options['token']})
changed = True
else:
changed, hook = self.updateHook(self.hookObject, {
'push_events': options['push_events'],
'push_events_branch_filter': options['push_events_branch_filter'],
'issues_events': options['issues_events'],
'merge_requests_events': options['merge_requests_events'],
'tag_push_events': options['tag_push_events'],
'note_events': options['note_events'],
'job_events': options['job_events'],
'pipeline_events': options['pipeline_events'],
'wiki_page_events': options['wiki_page_events'],
'enable_ssl_verification': options['enable_ssl_verification'],
'token': options['token']})
self.hookObject = hook
if changed:
if self._module.check_mode:
self._module.exit_json(changed=True, msg="Successfully created or updated the hook %s" % hook_url)
try:
hook.save()
except Exception as e:
self._module.fail_json(msg="Failed to update hook: %s " % e)
return True
else:
return False
'''
@param project Project Object
@param arguments Attributes of the hook
'''
def createHook(self, project, arguments):
if self._module.check_mode:
return True
hook = project.hooks.create(arguments)
return hook
'''
@param hook Hook Object
@param arguments Attributes of the hook
'''
def updateHook(self, hook, arguments):
changed = False
for arg_key, arg_value in arguments.items():
if arguments[arg_key] is not None:
if getattr(hook, arg_key) != arguments[arg_key]:
setattr(hook, arg_key, arguments[arg_key])
changed = True
return (changed, hook)
'''
@param project Project object
@param hook_url Url to call on event
'''
def findHook(self, project, hook_url):
hooks = project.hooks.list()
for hook in hooks:
if (hook.url == hook_url):
return hook
'''
@param project Project object
@param hook_url Url to call on event
'''
def existsHook(self, project, hook_url):
# When project exists, object will be stored in self.projectObject.
hook = self.findHook(project, hook_url)
if hook:
self.hookObject = hook
return True
return False
def deleteHook(self):
if self._module.check_mode:
return True
return self.hookObject.delete()
def main():
argument_spec = basic_auth_argument_spec()
argument_spec.update(dict(
api_token=dict(type='str', no_log=True),
state=dict(type='str', default="present", choices=["absent", "present"]),
project=dict(type='str', required=True),
hook_url=dict(type='str', required=True),
push_events=dict(type='bool', default=True),
push_events_branch_filter=dict(type='str', default=''),
issues_events=dict(type='bool', default=False),
merge_requests_events=dict(type='bool', default=False),
tag_push_events=dict(type='bool', default=False),
note_events=dict(type='bool', default=False),
job_events=dict(type='bool', default=False),
pipeline_events=dict(type='bool', default=False),
wiki_page_events=dict(type='bool', default=False),
hook_validate_certs=dict(type='bool', default=False, aliases=['enable_ssl_verification']),
token=dict(type='str', no_log=True),
))
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=[
['api_username', 'api_token'],
['api_password', 'api_token']
],
required_together=[
['api_username', 'api_password']
],
required_one_of=[
['api_username', 'api_token']
],
supports_check_mode=True,
)
state = module.params['state']
project_identifier = module.params['project']
hook_url = module.params['hook_url']
push_events = module.params['push_events']
push_events_branch_filter = module.params['push_events_branch_filter']
issues_events = module.params['issues_events']
merge_requests_events = module.params['merge_requests_events']
tag_push_events = module.params['tag_push_events']
note_events = module.params['note_events']
job_events = module.params['job_events']
pipeline_events = module.params['pipeline_events']
wiki_page_events = module.params['wiki_page_events']
enable_ssl_verification = module.params['hook_validate_certs']
hook_token = module.params['token']
if not HAS_GITLAB_PACKAGE:
module.fail_json(msg=missing_required_lib("python-gitlab"), exception=GITLAB_IMP_ERR)
gitlab_instance = gitlabAuthentication(module)
gitlab_hook = GitLabHook(module, gitlab_instance)
project = findProject(gitlab_instance, project_identifier)
if project is None:
module.fail_json(msg="Failed to create hook: project %s doesn't exists" % project_identifier)
hook_exists = gitlab_hook.existsHook(project, hook_url)
if state == 'absent':
if hook_exists:
gitlab_hook.deleteHook()
module.exit_json(changed=True, msg="Successfully deleted hook %s" % hook_url)
else:
module.exit_json(changed=False, msg="Hook deleted or does not exists")
if state == 'present':
if gitlab_hook.createOrUpdateHook(project, hook_url, {
"push_events": push_events,
"push_events_branch_filter": push_events_branch_filter,
"issues_events": issues_events,
"merge_requests_events": merge_requests_events,
"tag_push_events": tag_push_events,
"note_events": note_events,
"job_events": job_events,
"pipeline_events": pipeline_events,
"wiki_page_events": wiki_page_events,
"enable_ssl_verification": enable_ssl_verification,
"token": hook_token}):
module.exit_json(changed=True, msg="Successfully created or updated the hook %s" % hook_url, hook=gitlab_hook.hookObject._attrs)
else:
module.exit_json(changed=False, msg="No need to update the hook %s" % hook_url, hook=gitlab_hook.hookObject._attrs)
if __name__ == '__main__':
main()

View file

@ -0,0 +1 @@
gitlab_hook.py

View file

@ -0,0 +1,364 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Guillaume Martinez (lunik@tiwabbit.fr)
# Copyright: (c) 2015, Werner Dijkerman (ikben@werner-dijkerman.nl)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: gitlab_project
short_description: Creates/updates/deletes GitLab Projects
description:
- When the project does not exist in GitLab, it will be created.
- When the project does exists and state=absent, the project will be deleted.
- When changes are made to the project, the project will be updated.
author:
- Werner Dijkerman (@dj-wasabi)
- Guillaume Martinez (@Lunik)
requirements:
- python >= 2.7
- python-gitlab python module
extends_documentation_fragment:
- community.general.auth_basic
options:
api_token:
description:
- GitLab token for logging in.
type: str
group:
description:
- Id or The full path of the group of which this projects belongs to.
type: str
name:
description:
- The name of the project
required: true
type: str
path:
description:
- The path of the project you want to create, this will be server_url/<group>/path.
- If not supplied, name will be used.
type: str
description:
description:
- An description for the project.
type: str
issues_enabled:
description:
- Whether you want to create issues or not.
- Possible values are true and false.
type: bool
default: yes
merge_requests_enabled:
description:
- If merge requests can be made or not.
- Possible values are true and false.
type: bool
default: yes
wiki_enabled:
description:
- If an wiki for this project should be available or not.
- Possible values are true and false.
type: bool
default: yes
snippets_enabled:
description:
- If creating snippets should be available or not.
- Possible values are true and false.
type: bool
default: yes
visibility:
description:
- Private. Project access must be granted explicitly for each user.
- Internal. The project can be cloned by any logged in user.
- Public. The project can be cloned without any authentication.
default: private
type: str
choices: ["private", "internal", "public"]
aliases:
- visibility_level
import_url:
description:
- Git repository which will be imported into gitlab.
- GitLab server needs read access to this git repository.
required: false
type: str
state:
description:
- create or delete project.
- Possible values are present and absent.
default: present
type: str
choices: ["present", "absent"]
'''
EXAMPLES = '''
- name: Delete GitLab Project
gitlab_project:
api_url: https://gitlab.example.com/
api_token: "{{ access_token }}"
validate_certs: False
name: my_first_project
state: absent
delegate_to: localhost
- name: Create GitLab Project in group Ansible
gitlab_project:
api_url: https://gitlab.example.com/
validate_certs: True
api_username: dj-wasabi
api_password: "MySecretPassword"
name: my_first_project
group: ansible
issues_enabled: False
wiki_enabled: True
snippets_enabled: True
import_url: http://git.example.com/example/lab.git
state: present
delegate_to: localhost
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Success"
result:
description: json parsed response from the server
returned: always
type: dict
error:
description: the error message returned by the GitLab API
returned: failed
type: str
sample: "400: path is already in use"
project:
description: API object
returned: always
type: dict
'''
import traceback
GITLAB_IMP_ERR = None
try:
import gitlab
HAS_GITLAB_PACKAGE = True
except Exception:
GITLAB_IMP_ERR = traceback.format_exc()
HAS_GITLAB_PACKAGE = False
from ansible.module_utils.api import basic_auth_argument_spec
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native
from ansible_collections.community.general.plugins.module_utils.gitlab import findGroup, findProject, gitlabAuthentication
class GitLabProject(object):
def __init__(self, module, gitlab_instance):
self._module = module
self._gitlab = gitlab_instance
self.projectObject = None
'''
@param project_name Name of the project
@param namespace Namespace Object (User or Group)
@param options Options of the project
'''
def createOrUpdateProject(self, project_name, namespace, options):
changed = False
# Because we have already call userExists in main()
if self.projectObject is None:
project = self.createProject(namespace, {
'name': project_name,
'path': options['path'],
'description': options['description'],
'issues_enabled': options['issues_enabled'],
'merge_requests_enabled': options['merge_requests_enabled'],
'wiki_enabled': options['wiki_enabled'],
'snippets_enabled': options['snippets_enabled'],
'visibility': options['visibility'],
'import_url': options['import_url']})
changed = True
else:
changed, project = self.updateProject(self.projectObject, {
'name': project_name,
'description': options['description'],
'issues_enabled': options['issues_enabled'],
'merge_requests_enabled': options['merge_requests_enabled'],
'wiki_enabled': options['wiki_enabled'],
'snippets_enabled': options['snippets_enabled'],
'visibility': options['visibility']})
self.projectObject = project
if changed:
if self._module.check_mode:
self._module.exit_json(changed=True, msg="Successfully created or updated the project %s" % project_name)
try:
project.save()
except Exception as e:
self._module.fail_json(msg="Failed update project: %s " % e)
return True
else:
return False
'''
@param namespace Namespace Object (User or Group)
@param arguments Attributes of the project
'''
def createProject(self, namespace, arguments):
if self._module.check_mode:
return True
arguments['namespace_id'] = namespace.id
try:
project = self._gitlab.projects.create(arguments)
except (gitlab.exceptions.GitlabCreateError) as e:
self._module.fail_json(msg="Failed to create project: %s " % to_native(e))
return project
'''
@param project Project Object
@param arguments Attributes of the project
'''
def updateProject(self, project, arguments):
changed = False
for arg_key, arg_value in arguments.items():
if arguments[arg_key] is not None:
if getattr(project, arg_key) != arguments[arg_key]:
setattr(project, arg_key, arguments[arg_key])
changed = True
return (changed, project)
def deleteProject(self):
if self._module.check_mode:
return True
project = self.projectObject
return project.delete()
'''
@param namespace User/Group object
@param name Name of the project
'''
def existsProject(self, namespace, path):
# When project exists, object will be stored in self.projectObject.
project = findProject(self._gitlab, namespace.full_path + '/' + path)
if project:
self.projectObject = project
return True
return False
def main():
argument_spec = basic_auth_argument_spec()
argument_spec.update(dict(
api_token=dict(type='str', no_log=True),
group=dict(type='str'),
name=dict(type='str', required=True),
path=dict(type='str'),
description=dict(type='str'),
issues_enabled=dict(type='bool', default=True),
merge_requests_enabled=dict(type='bool', default=True),
wiki_enabled=dict(type='bool', default=True),
snippets_enabled=dict(default=True, type='bool'),
visibility=dict(type='str', default="private", choices=["internal", "private", "public"], aliases=["visibility_level"]),
import_url=dict(type='str'),
state=dict(type='str', default="present", choices=["absent", "present"]),
))
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=[
['api_username', 'api_token'],
['api_password', 'api_token'],
],
required_together=[
['api_username', 'api_password'],
],
required_one_of=[
['api_username', 'api_token']
],
supports_check_mode=True,
)
group_identifier = module.params['group']
project_name = module.params['name']
project_path = module.params['path']
project_description = module.params['description']
issues_enabled = module.params['issues_enabled']
merge_requests_enabled = module.params['merge_requests_enabled']
wiki_enabled = module.params['wiki_enabled']
snippets_enabled = module.params['snippets_enabled']
visibility = module.params['visibility']
import_url = module.params['import_url']
state = module.params['state']
if not HAS_GITLAB_PACKAGE:
module.fail_json(msg=missing_required_lib("python-gitlab"), exception=GITLAB_IMP_ERR)
gitlab_instance = gitlabAuthentication(module)
# Set project_path to project_name if it is empty.
if project_path is None:
project_path = project_name.replace(" ", "_")
gitlab_project = GitLabProject(module, gitlab_instance)
if group_identifier:
group = findGroup(gitlab_instance, group_identifier)
if group is None:
module.fail_json(msg="Failed to create project: group %s doesn't exists" % group_identifier)
namespace = gitlab_instance.namespaces.get(group.id)
project_exists = gitlab_project.existsProject(namespace, project_path)
else:
user = gitlab_instance.users.list(username=gitlab_instance.user.username)[0]
namespace = gitlab_instance.namespaces.get(user.id)
project_exists = gitlab_project.existsProject(namespace, project_path)
if state == 'absent':
if project_exists:
gitlab_project.deleteProject()
module.exit_json(changed=True, msg="Successfully deleted project %s" % project_name)
else:
module.exit_json(changed=False, msg="Project deleted or does not exists")
if state == 'present':
if gitlab_project.createOrUpdateProject(project_name, namespace, {
"path": project_path,
"description": project_description,
"issues_enabled": issues_enabled,
"merge_requests_enabled": merge_requests_enabled,
"wiki_enabled": wiki_enabled,
"snippets_enabled": snippets_enabled,
"visibility": visibility,
"import_url": import_url}):
module.exit_json(changed=True, msg="Successfully created or updated the project %s" % project_name, project=gitlab_project.projectObject._attrs)
else:
module.exit_json(changed=False, msg="No need to update the project %s" % project_name, project=gitlab_project.projectObject._attrs)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,291 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Markus Bergholz (markuman@gmail.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
module: gitlab_project_variable
short_description: Creates/updates/deletes GitLab Projects Variables
description:
- When a project variable does not exist, it will be created.
- When a project variable does exist, its value will be updated when the values are different.
- Variables which are untouched in the playbook, but are not untouched in the GitLab project,
they stay untouched (I(purge) is C(false)) or will be deleted (I(purge) is C(true)).
author:
- "Markus Bergholz (@markuman)"
requirements:
- python >= 2.7
- python-gitlab python module
extends_documentation_fragment:
- community.general.auth_basic
options:
state:
description:
- Create or delete project variable.
- Possible values are present and absent.
default: present
type: str
choices: ["present", "absent"]
api_token:
description:
- GitLab access token with API permissions.
required: true
type: str
project:
description:
- The path and name of the project.
required: true
type: str
purge:
description:
- When set to true, all variables which are not untouched in the task will be deleted.
default: false
type: bool
vars:
description:
- When the list element is a simple key-value pair, masked and protected will be set to false.
- When the list element is a dict with the keys I(value), I(masked) and I(protected), the user can
have full control about whether a value should be masked, protected or both.
- Support for protected values requires GitLab >= 9.3.
- Support for masked values requires GitLab >= 11.10.
- A I(value) must be a string or a number.
- When a value is masked, it must be in Base64 and have a length of at least 8 characters.
See GitLab documentation on acceptable values for a masked variable (https://docs.gitlab.com/ce/ci/variables/#masked-variables).
default: {}
type: dict
'''
EXAMPLES = '''
- name: Set or update some CI/CD variables
gitlab_project_variable:
api_url: https://gitlab.com
api_token: secret_access_token
project: markuman/dotfiles
purge: false
vars:
ACCESS_KEY_ID: abc123
SECRET_ACCESS_KEY: 321cba
- name: Set or update some CI/CD variables
gitlab_project_variable:
api_url: https://gitlab.com
api_token: secret_access_token
project: markuman/dotfiles
purge: false
vars:
ACCESS_KEY_ID: abc123
SECRET_ACCESS_KEY:
value: 3214cbad
masked: true
protected: true
- name: Delete one variable
gitlab_project_variable:
api_url: https://gitlab.com
api_token: secret_access_token
project: markuman/dotfiles
state: absent
vars:
ACCESS_KEY_ID: abc123
'''
RETURN = '''
project_variable:
description: Four lists of the variablenames which were added, updated, removed or exist.
returned: always
type: dict
contains:
added:
description: A list of variables which were created.
returned: always
type: list
sample: "['ACCESS_KEY_ID', 'SECRET_ACCESS_KEY']"
untouched:
description: A list of variables which exist.
returned: always
type: list
sample: "['ACCESS_KEY_ID', 'SECRET_ACCESS_KEY']"
removed:
description: A list of variables which were deleted.
returned: always
type: list
sample: "['ACCESS_KEY_ID', 'SECRET_ACCESS_KEY']"
updated:
description: A list of variables whose values were changed.
returned: always
type: list
sample: "['ACCESS_KEY_ID', 'SECRET_ACCESS_KEY']"
'''
import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native
from ansible.module_utils.api import basic_auth_argument_spec
from ansible.module_utils.six import string_types
from ansible.module_utils.six import integer_types
GITLAB_IMP_ERR = None
try:
import gitlab
HAS_GITLAB_PACKAGE = True
except Exception:
GITLAB_IMP_ERR = traceback.format_exc()
HAS_GITLAB_PACKAGE = False
from ansible_collections.community.general.plugins.module_utils.gitlab import gitlabAuthentication
class GitlabProjectVariables(object):
def __init__(self, module, gitlab_instance):
self.repo = gitlab_instance
self.project = self.get_project(module.params['project'])
self._module = module
def get_project(self, project_name):
return self.repo.projects.get(project_name)
def list_all_project_variables(self):
return self.project.variables.list()
def create_variable(self, key, value, masked, protected):
if self._module.check_mode:
return
return self.project.variables.create({"key": key, "value": value,
"masked": masked, "protected": protected})
def update_variable(self, key, var, value, masked, protected):
if var.value == value and var.protected == protected and var.masked == masked:
return False
if self._module.check_mode:
return True
if var.protected == protected and var.masked == masked:
var.value = value
var.save()
return True
self.delete_variable(key)
self.create_variable(key, value, masked, protected)
return True
def delete_variable(self, key):
if self._module.check_mode:
return
return self.project.variables.delete(key)
def native_python_main(this_gitlab, purge, var_list, state, module):
change = False
return_value = dict(added=list(), updated=list(), removed=list(), untouched=list())
gitlab_keys = this_gitlab.list_all_project_variables()
existing_variables = [x.get_id() for x in gitlab_keys]
for key in var_list:
if isinstance(var_list[key], string_types) or isinstance(var_list[key], (integer_types, float)):
value = var_list[key]
masked = False
protected = False
elif isinstance(var_list[key], dict):
value = var_list[key].get('value')
masked = var_list[key].get('masked', False)
protected = var_list[key].get('protected', False)
else:
module.fail_json(msg="value must be of type string, integer or dict")
if key in existing_variables:
index = existing_variables.index(key)
existing_variables[index] = None
if state == 'present':
single_change = this_gitlab.update_variable(key,
gitlab_keys[index],
value, masked,
protected)
change = single_change or change
if single_change:
return_value['updated'].append(key)
else:
return_value['untouched'].append(key)
elif state == 'absent':
this_gitlab.delete_variable(key)
change = True
return_value['removed'].append(key)
elif key not in existing_variables and state == 'present':
this_gitlab.create_variable(key, value, masked, protected)
change = True
return_value['added'].append(key)
existing_variables = list(filter(None, existing_variables))
if purge:
for item in existing_variables:
this_gitlab.delete_variable(item)
change = True
return_value['removed'].append(item)
else:
return_value['untouched'].extend(existing_variables)
return change, return_value
def main():
argument_spec = basic_auth_argument_spec()
argument_spec.update(
api_token=dict(type='str', required=True, no_log=True),
project=dict(type='str', required=True),
purge=dict(type='bool', required=False, default=False),
vars=dict(type='dict', required=False, default=dict(), no_log=True),
state=dict(type='str', default="present", choices=["absent", "present"])
)
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=[
['api_username', 'api_token'],
['api_password', 'api_token'],
],
required_together=[
['api_username', 'api_password'],
],
required_one_of=[
['api_username', 'api_token']
],
supports_check_mode=True
)
purge = module.params['purge']
var_list = module.params['vars']
state = module.params['state']
if not HAS_GITLAB_PACKAGE:
module.fail_json(msg=missing_required_lib("python-gitlab"), exception=GITLAB_IMP_ERR)
gitlab_instance = gitlabAuthentication(module)
this_gitlab = GitlabProjectVariables(module=module, gitlab_instance=gitlab_instance)
change, return_value = native_python_main(this_gitlab, purge, var_list, state, module)
module.exit_json(changed=change, project_variable=return_value)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,353 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Guillaume Martinez (lunik@tiwabbit.fr)
# Copyright: (c) 2018, Samy Coenen <samy.coenen@nubera.be>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: gitlab_runner
short_description: Create, modify and delete GitLab Runners.
description:
- Register, update and delete runners with the GitLab API.
- All operations are performed using the GitLab API v4.
- For details, consult the full API documentation at U(https://docs.gitlab.com/ee/api/runners.html).
- A valid private API token is required for all operations. You can create as many tokens as you like using the GitLab web interface at
U(https://$GITLAB_URL/profile/personal_access_tokens).
- A valid registration token is required for registering a new runner.
To create shared runners, you need to ask your administrator to give you this token.
It can be found at U(https://$GITLAB_URL/admin/runners/).
notes:
- To create a new runner at least the C(api_token), C(description) and C(api_url) options are required.
- Runners need to have unique descriptions.
author:
- Samy Coenen (@SamyCoenen)
- Guillaume Martinez (@Lunik)
requirements:
- python >= 2.7
- python-gitlab >= 1.5.0
extends_documentation_fragment:
- community.general.auth_basic
options:
api_token:
description:
- Your private token to interact with the GitLab API.
required: True
type: str
description:
description:
- The unique name of the runner.
required: True
type: str
aliases:
- name
state:
description:
- Make sure that the runner with the same name exists with the same configuration or delete the runner with the same name.
required: False
default: present
choices: ["present", "absent"]
type: str
registration_token:
description:
- The registration token is used to register new runners.
required: True
type: str
active:
description:
- Define if the runners is immediately active after creation.
required: False
default: yes
type: bool
locked:
description:
- Determines if the runner is locked or not.
required: False
default: False
type: bool
access_level:
description:
- Determines if a runner can pick up jobs from protected branches.
required: False
default: ref_protected
choices: ["ref_protected", "not_protected"]
type: str
maximum_timeout:
description:
- The maximum timeout that a runner has to pick up a specific job.
required: False
default: 3600
type: int
run_untagged:
description:
- Run untagged jobs or not.
required: False
default: yes
type: bool
tag_list:
description: The tags that apply to the runner.
required: False
default: []
type: list
'''
EXAMPLES = '''
- name: "Register runner"
gitlab_runner:
api_url: https://gitlab.example.com/
api_token: "{{ access_token }}"
registration_token: 4gfdsg345
description: Docker Machine t1
state: present
active: True
tag_list: ['docker']
run_untagged: False
locked: False
- name: "Delete runner"
gitlab_runner:
api_url: https://gitlab.example.com/
api_token: "{{ access_token }}"
description: Docker Machine t1
state: absent
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Success"
result:
description: json parsed response from the server
returned: always
type: dict
error:
description: the error message returned by the GitLab API
returned: failed
type: str
sample: "400: path is already in use"
runner:
description: API object
returned: always
type: dict
'''
import traceback
GITLAB_IMP_ERR = None
try:
import gitlab
HAS_GITLAB_PACKAGE = True
except Exception:
GITLAB_IMP_ERR = traceback.format_exc()
HAS_GITLAB_PACKAGE = False
from ansible.module_utils.api import basic_auth_argument_spec
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native
from ansible_collections.community.general.plugins.module_utils.gitlab import gitlabAuthentication
try:
cmp
except NameError:
def cmp(a, b):
return (a > b) - (a < b)
class GitLabRunner(object):
def __init__(self, module, gitlab_instance):
self._module = module
self._gitlab = gitlab_instance
self.runnerObject = None
def createOrUpdateRunner(self, description, options):
changed = False
# Because we have already call userExists in main()
if self.runnerObject is None:
runner = self.createRunner({
'description': description,
'active': options['active'],
'token': options['registration_token'],
'locked': options['locked'],
'run_untagged': options['run_untagged'],
'maximum_timeout': options['maximum_timeout'],
'tag_list': options['tag_list']})
changed = True
else:
changed, runner = self.updateRunner(self.runnerObject, {
'active': options['active'],
'locked': options['locked'],
'run_untagged': options['run_untagged'],
'maximum_timeout': options['maximum_timeout'],
'access_level': options['access_level'],
'tag_list': options['tag_list']})
self.runnerObject = runner
if changed:
if self._module.check_mode:
self._module.exit_json(changed=True, msg="Successfully created or updated the runner %s" % description)
try:
runner.save()
except Exception as e:
self._module.fail_json(msg="Failed to update runner: %s " % to_native(e))
return True
else:
return False
'''
@param arguments Attributes of the runner
'''
def createRunner(self, arguments):
if self._module.check_mode:
return True
try:
runner = self._gitlab.runners.create(arguments)
except (gitlab.exceptions.GitlabCreateError) as e:
self._module.fail_json(msg="Failed to create runner: %s " % to_native(e))
return runner
'''
@param runner Runner object
@param arguments Attributes of the runner
'''
def updateRunner(self, runner, arguments):
changed = False
for arg_key, arg_value in arguments.items():
if arguments[arg_key] is not None:
if isinstance(arguments[arg_key], list):
list1 = getattr(runner, arg_key)
list1.sort()
list2 = arguments[arg_key]
list2.sort()
if cmp(list1, list2):
setattr(runner, arg_key, arguments[arg_key])
changed = True
else:
if getattr(runner, arg_key) != arguments[arg_key]:
setattr(runner, arg_key, arguments[arg_key])
changed = True
return (changed, runner)
'''
@param description Description of the runner
'''
def findRunner(self, description):
runners = self._gitlab.runners.all(as_list=False)
for runner in runners:
if (runner['description'] == description):
return self._gitlab.runners.get(runner['id'])
'''
@param description Description of the runner
'''
def existsRunner(self, description):
# When runner exists, object will be stored in self.runnerObject.
runner = self.findRunner(description)
if runner:
self.runnerObject = runner
return True
return False
def deleteRunner(self):
if self._module.check_mode:
return True
runner = self.runnerObject
return runner.delete()
def main():
argument_spec = basic_auth_argument_spec()
argument_spec.update(dict(
api_token=dict(type='str', no_log=True),
description=dict(type='str', required=True, aliases=["name"]),
active=dict(type='bool', default=True),
tag_list=dict(type='list', default=[]),
run_untagged=dict(type='bool', default=True),
locked=dict(type='bool', default=False),
access_level=dict(type='str', default='ref_protected', choices=["not_protected", "ref_protected"]),
maximum_timeout=dict(type='int', default=3600),
registration_token=dict(type='str', required=True),
state=dict(type='str', default="present", choices=["absent", "present"]),
))
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=[
['api_username', 'api_token'],
['api_password', 'api_token'],
],
required_together=[
['api_username', 'api_password'],
],
required_one_of=[
['api_username', 'api_token'],
],
supports_check_mode=True,
)
state = module.params['state']
runner_description = module.params['description']
runner_active = module.params['active']
tag_list = module.params['tag_list']
run_untagged = module.params['run_untagged']
runner_locked = module.params['locked']
access_level = module.params['access_level']
maximum_timeout = module.params['maximum_timeout']
registration_token = module.params['registration_token']
if not HAS_GITLAB_PACKAGE:
module.fail_json(msg=missing_required_lib("python-gitlab"), exception=GITLAB_IMP_ERR)
gitlab_instance = gitlabAuthentication(module)
gitlab_runner = GitLabRunner(module, gitlab_instance)
runner_exists = gitlab_runner.existsRunner(runner_description)
if state == 'absent':
if runner_exists:
gitlab_runner.deleteRunner()
module.exit_json(changed=True, msg="Successfully deleted runner %s" % runner_description)
else:
module.exit_json(changed=False, msg="Runner deleted or does not exists")
if state == 'present':
if gitlab_runner.createOrUpdateRunner(runner_description, {
"active": runner_active,
"tag_list": tag_list,
"run_untagged": run_untagged,
"locked": runner_locked,
"access_level": access_level,
"maximum_timeout": maximum_timeout,
"registration_token": registration_token}):
module.exit_json(changed=True, runner=gitlab_runner.runnerObject._attrs,
msg="Successfully created or updated the runner %s" % runner_description)
else:
module.exit_json(changed=False, runner=gitlab_runner.runnerObject._attrs,
msg="No need to update the runner %s" % runner_description)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,477 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Guillaume Martinez (lunik@tiwabbit.fr)
# Copyright: (c) 2015, Werner Dijkerman (ikben@werner-dijkerman.nl)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: gitlab_user
short_description: Creates/updates/deletes GitLab Users
description:
- When the user does not exist in GitLab, it will be created.
- When the user does exists and state=absent, the user will be deleted.
- When changes are made to user, the user will be updated.
notes:
- From Ansible 2.10 and onwards, name, email and password are optional while deleting the user.
author:
- Werner Dijkerman (@dj-wasabi)
- Guillaume Martinez (@Lunik)
requirements:
- python >= 2.7
- python-gitlab python module
- administrator rights on the GitLab server
extends_documentation_fragment:
- community.general.auth_basic
options:
api_token:
description:
- GitLab token for logging in.
type: str
name:
description:
- Name of the user you want to create.
- Required only if C(state) is set to C(present).
type: str
username:
description:
- The username of the user.
required: true
type: str
password:
description:
- The password of the user.
- GitLab server enforces minimum password length to 8, set this value with 8 or more characters.
- Required only if C(state) is set to C(present).
type: str
email:
description:
- The email that belongs to the user.
- Required only if C(state) is set to C(present).
type: str
sshkey_name:
description:
- The name of the sshkey
type: str
sshkey_file:
description:
- The ssh key itself.
type: str
group:
description:
- Id or Full path of parent group in the form of group/name.
- Add user as an member to this group.
type: str
access_level:
description:
- The access level to the group. One of the following can be used.
- guest
- reporter
- developer
- master (alias for maintainer)
- maintainer
- owner
default: guest
type: str
choices: ["guest", "reporter", "developer", "master", "maintainer", "owner"]
state:
description:
- create or delete group.
- Possible values are present and absent.
default: present
type: str
choices: ["present", "absent"]
confirm:
description:
- Require confirmation.
type: bool
default: yes
isadmin:
description:
- Grant admin privileges to the user.
type: bool
default: no
external:
description:
- Define external parameter for this user.
type: bool
default: no
'''
EXAMPLES = '''
- name: "Delete GitLab User"
gitlab_user:
api_url: https://gitlab.example.com/
api_token: "{{ access_token }}"
validate_certs: False
username: myusername
state: absent
delegate_to: localhost
- name: "Create GitLab User"
gitlab_user:
api_url: https://gitlab.example.com/
validate_certs: True
api_username: dj-wasabi
api_password: "MySecretPassword"
name: My Name
username: myusername
password: mysecretpassword
email: me@example.com
sshkey_name: MySSH
sshkey_file: ssh-rsa AAAAB3NzaC1yc...
state: present
group: super_group/mon_group
access_level: owner
delegate_to: localhost
'''
RETURN = '''
msg:
description: Success or failure message
returned: always
type: str
sample: "Success"
result:
description: json parsed response from the server
returned: always
type: dict
error:
description: the error message returned by the GitLab API
returned: failed
type: str
sample: "400: path is already in use"
user:
description: API object
returned: always
type: dict
'''
import traceback
GITLAB_IMP_ERR = None
try:
import gitlab
HAS_GITLAB_PACKAGE = True
except Exception:
GITLAB_IMP_ERR = traceback.format_exc()
HAS_GITLAB_PACKAGE = False
from ansible.module_utils.api import basic_auth_argument_spec
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native
from ansible_collections.community.general.plugins.module_utils.gitlab import findGroup, gitlabAuthentication
class GitLabUser(object):
def __init__(self, module, gitlab_instance):
self._module = module
self._gitlab = gitlab_instance
self.userObject = None
self.ACCESS_LEVEL = {
'guest': gitlab.GUEST_ACCESS,
'reporter': gitlab.REPORTER_ACCESS,
'developer': gitlab.DEVELOPER_ACCESS,
'master': gitlab.MAINTAINER_ACCESS,
'maintainer': gitlab.MAINTAINER_ACCESS,
'owner': gitlab.OWNER_ACCESS}
'''
@param username Username of the user
@param options User options
'''
def createOrUpdateUser(self, username, options):
changed = False
# Because we have already call userExists in main()
if self.userObject is None:
user = self.createUser({
'name': options['name'],
'username': username,
'password': options['password'],
'email': options['email'],
'skip_confirmation': not options['confirm'],
'admin': options['isadmin'],
'external': options['external']})
changed = True
else:
changed, user = self.updateUser(self.userObject, {
'name': options['name'],
'email': options['email'],
'is_admin': options['isadmin'],
'external': options['external']})
# Assign ssh keys
if options['sshkey_name'] and options['sshkey_file']:
key_changed = self.addSshKeyToUser(user, {
'name': options['sshkey_name'],
'file': options['sshkey_file']})
changed = changed or key_changed
# Assign group
if options['group_path']:
group_changed = self.assignUserToGroup(user, options['group_path'], options['access_level'])
changed = changed or group_changed
self.userObject = user
if changed:
if self._module.check_mode:
self._module.exit_json(changed=True, msg="Successfully created or updated the user %s" % username)
try:
user.save()
except Exception as e:
self._module.fail_json(msg="Failed to update user: %s " % to_native(e))
return True
else:
return False
'''
@param group User object
'''
def getUserId(self, user):
if user is not None:
return user.id
return None
'''
@param user User object
@param sshkey_name Name of the ssh key
'''
def sshKeyExists(self, user, sshkey_name):
keyList = map(lambda k: k.title, user.keys.list())
return sshkey_name in keyList
'''
@param user User object
@param sshkey Dict containing sshkey infos {"name": "", "file": ""}
'''
def addSshKeyToUser(self, user, sshkey):
if not self.sshKeyExists(user, sshkey['name']):
if self._module.check_mode:
return True
try:
user.keys.create({
'title': sshkey['name'],
'key': sshkey['file']})
except gitlab.exceptions.GitlabCreateError as e:
self._module.fail_json(msg="Failed to assign sshkey to user: %s" % to_native(e))
return True
return False
'''
@param group Group object
@param user_id Id of the user to find
'''
def findMember(self, group, user_id):
try:
member = group.members.get(user_id)
except gitlab.exceptions.GitlabGetError:
return None
return member
'''
@param group Group object
@param user_id Id of the user to check
'''
def memberExists(self, group, user_id):
member = self.findMember(group, user_id)
return member is not None
'''
@param group Group object
@param user_id Id of the user to check
@param access_level GitLab access_level to check
'''
def memberAsGoodAccessLevel(self, group, user_id, access_level):
member = self.findMember(group, user_id)
return member.access_level == access_level
'''
@param user User object
@param group_path Complete path of the Group including parent group path. <parent_path>/<group_path>
@param access_level GitLab access_level to assign
'''
def assignUserToGroup(self, user, group_identifier, access_level):
group = findGroup(self._gitlab, group_identifier)
if self._module.check_mode:
return True
if group is None:
return False
if self.memberExists(group, self.getUserId(user)):
member = self.findMember(group, self.getUserId(user))
if not self.memberAsGoodAccessLevel(group, member.id, self.ACCESS_LEVEL[access_level]):
member.access_level = self.ACCESS_LEVEL[access_level]
member.save()
return True
else:
try:
group.members.create({
'user_id': self.getUserId(user),
'access_level': self.ACCESS_LEVEL[access_level]})
except gitlab.exceptions.GitlabCreateError as e:
self._module.fail_json(msg="Failed to assign user to group: %s" % to_native(e))
return True
return False
'''
@param user User object
@param arguments User attributes
'''
def updateUser(self, user, arguments):
changed = False
for arg_key, arg_value in arguments.items():
if arguments[arg_key] is not None:
if getattr(user, arg_key) != arguments[arg_key]:
setattr(user, arg_key, arguments[arg_key])
changed = True
return (changed, user)
'''
@param arguments User attributes
'''
def createUser(self, arguments):
if self._module.check_mode:
return True
try:
user = self._gitlab.users.create(arguments)
except (gitlab.exceptions.GitlabCreateError) as e:
self._module.fail_json(msg="Failed to create user: %s " % to_native(e))
return user
'''
@param username Username of the user
'''
def findUser(self, username):
users = self._gitlab.users.list(search=username)
for user in users:
if (user.username == username):
return user
'''
@param username Username of the user
'''
def existsUser(self, username):
# When user exists, object will be stored in self.userObject.
user = self.findUser(username)
if user:
self.userObject = user
return True
return False
def deleteUser(self):
if self._module.check_mode:
return True
user = self.userObject
return user.delete()
def main():
argument_spec = basic_auth_argument_spec()
argument_spec.update(dict(
api_token=dict(type='str', no_log=True),
name=dict(type='str'),
state=dict(type='str', default="present", choices=["absent", "present"]),
username=dict(type='str', required=True),
password=dict(type='str', no_log=True),
email=dict(type='str'),
sshkey_name=dict(type='str'),
sshkey_file=dict(type='str'),
group=dict(type='str'),
access_level=dict(type='str', default="guest", choices=["developer", "guest", "maintainer", "master", "owner", "reporter"]),
confirm=dict(type='bool', default=True),
isadmin=dict(type='bool', default=False),
external=dict(type='bool', default=False),
))
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=[
['api_username', 'api_token'],
['api_password', 'api_token'],
],
required_together=[
['api_username', 'api_password'],
],
required_one_of=[
['api_username', 'api_token']
],
supports_check_mode=True,
required_if=(
('state', 'present', ['name', 'email', 'password']),
)
)
user_name = module.params['name']
state = module.params['state']
user_username = module.params['username'].lower()
user_password = module.params['password']
user_email = module.params['email']
user_sshkey_name = module.params['sshkey_name']
user_sshkey_file = module.params['sshkey_file']
group_path = module.params['group']
access_level = module.params['access_level']
confirm = module.params['confirm']
user_isadmin = module.params['isadmin']
user_external = module.params['external']
if not HAS_GITLAB_PACKAGE:
module.fail_json(msg=missing_required_lib("python-gitlab"), exception=GITLAB_IMP_ERR)
gitlab_instance = gitlabAuthentication(module)
gitlab_user = GitLabUser(module, gitlab_instance)
user_exists = gitlab_user.existsUser(user_username)
if state == 'absent':
if user_exists:
gitlab_user.deleteUser()
module.exit_json(changed=True, msg="Successfully deleted user %s" % user_username)
else:
module.exit_json(changed=False, msg="User deleted or does not exists")
if state == 'present':
if gitlab_user.createOrUpdateUser(user_username, {
"name": user_name,
"password": user_password,
"email": user_email,
"sshkey_name": user_sshkey_name,
"sshkey_file": user_sshkey_file,
"group_path": group_path,
"access_level": access_level,
"confirm": confirm,
"isadmin": user_isadmin,
"external": user_external}):
module.exit_json(changed=True, msg="Successfully created or updated the user %s" % user_username, user=gitlab_user.userObject._attrs)
else:
module.exit_json(changed=False, msg="No need to update the user %s" % user_username, user=gitlab_user.userObject._attrs)
if __name__ == '__main__':
main()

View file

@ -0,0 +1,296 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2013, Yeukhon Wong <yeukhon@acm.org>
# Copyright: (c) 2014, Nate Coraor <nate@bx.psu.edu>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: hg
short_description: Manages Mercurial (hg) repositories
description:
- Manages Mercurial (hg) repositories. Supports SSH, HTTP/S and local address.
author: "Yeukhon Wong (@yeukhon)"
options:
repo:
description:
- The repository address.
required: yes
aliases: [ name ]
dest:
description:
- Absolute path of where the repository should be cloned to.
This parameter is required, unless clone and update are set to no
required: yes
revision:
description:
- Equivalent C(-r) option in hg command which could be the changeset, revision number,
branch name or even tag.
aliases: [ version ]
force:
description:
- Discards uncommitted changes. Runs C(hg update -C). Prior to
1.9, the default was `yes`.
type: bool
default: 'no'
purge:
description:
- Deletes untracked files. Runs C(hg purge).
type: bool
default: 'no'
update:
description:
- If C(no), do not retrieve new revisions from the origin repository
type: bool
default: 'yes'
clone:
description:
- If C(no), do not clone the repository if it does not exist locally.
type: bool
default: 'yes'
executable:
description:
- Path to hg executable to use. If not supplied,
the normal mechanism for resolving binary paths will be used.
notes:
- This module does not support push capability. See U(https://github.com/ansible/ansible/issues/31156).
- "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. To avoid this prompt,
one solution is to add the remote host public key in C(/etc/ssh/ssh_known_hosts) before calling
the hg module, with the following command: ssh-keyscan remote_host.com >> /etc/ssh/ssh_known_hosts."
- As per 01 Dec 2018, Bitbucket has dropped support for TLSv1 and TLSv1.1 connections. As such,
if the underlying system still uses a Python version below 2.7.9, you will have issues checking out
bitbucket repositories. See U(https://bitbucket.org/blog/deprecating-tlsv1-tlsv1-1-2018-12-01).
'''
EXAMPLES = '''
- name: Ensure the current working copy is inside the stable branch and deletes untracked files if any.
hg:
repo: https://bitbucket.org/user/repo1
dest: /home/user/repo1
revision: stable
purge: yes
- name: Get information about the repository whether or not it has already been cloned locally.
hg:
repo: git://bitbucket.org/user/repo
dest: /srv/checkout
clone: no
update: no
'''
import os
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
class Hg(object):
def __init__(self, module, dest, repo, revision, hg_path):
self.module = module
self.dest = dest
self.repo = repo
self.revision = revision
self.hg_path = hg_path
def _command(self, args_list):
(rc, out, err) = self.module.run_command([self.hg_path] + args_list)
return (rc, out, err)
def _list_untracked(self):
args = ['purge', '--config', 'extensions.purge=', '-R', self.dest, '--print']
return self._command(args)
def get_revision(self):
"""
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) = self._command(['id', '-b', '-i', '-t', '-R', self.dest])
if rc != 0:
self.module.fail_json(msg=err)
else:
return to_native(out).strip('\n')
def get_remote_revision(self):
(rc, out, err) = self._command(['id', self.repo])
if rc != 0:
self.module.fail_json(msg=err)
else:
return to_native(out).strip('\n')
def has_local_mods(self):
now = self.get_revision()
if '+' in now:
return True
else:
return False
def discard(self):
before = self.has_local_mods()
if not before:
return False
args = ['update', '-C', '-R', self.dest, '-r', '.']
(rc, out, err) = self._command(args)
if rc != 0:
self.module.fail_json(msg=err)
after = self.has_local_mods()
if before != after and not after: # no more local modification
return True
def purge(self):
# before purge, find out if there are any untracked files
(rc1, out1, err1) = self._list_untracked()
if rc1 != 0:
self.module.fail_json(msg=err1)
# there are some untrackd files
if out1 != '':
args = ['purge', '--config', 'extensions.purge=', '-R', self.dest]
(rc2, out2, err2) = self._command(args)
if rc2 != 0:
self.module.fail_json(msg=err2)
return True
else:
return False
def cleanup(self, force, purge):
discarded = False
purged = False
if force:
discarded = self.discard()
if purge:
purged = self.purge()
if discarded or purged:
return True
else:
return False
def pull(self):
return self._command(
['pull', '-R', self.dest, self.repo])
def update(self):
if self.revision is not None:
return self._command(['update', '-r', self.revision, '-R', self.dest])
return self._command(['update', '-R', self.dest])
def clone(self):
if self.revision is not None:
return self._command(['clone', self.repo, self.dest, '-r', self.revision])
return self._command(['clone', self.repo, self.dest])
@property
def at_revision(self):
"""
There is no point in pulling from a potentially down/slow remote site
if the desired changeset is already the current changeset.
"""
if self.revision is None or len(self.revision) < 7:
# Assume it's a rev number, tag, or branch
return False
(rc, out, err) = self._command(['--debug', 'id', '-i', '-R', self.dest])
if rc != 0:
self.module.fail_json(msg=err)
if out.startswith(self.revision):
return True
return False
# ===========================================
def main():
module = AnsibleModule(
argument_spec=dict(
repo=dict(type='str', required=True, aliases=['name']),
dest=dict(type='path'),
revision=dict(type='str', default=None, aliases=['version']),
force=dict(type='bool', default=False),
purge=dict(type='bool', default=False),
update=dict(type='bool', default=True),
clone=dict(type='bool', default=True),
executable=dict(type='str', default=None),
),
)
repo = module.params['repo']
dest = module.params['dest']
revision = module.params['revision']
force = module.params['force']
purge = module.params['purge']
update = module.params['update']
clone = module.params['clone']
hg_path = module.params['executable'] or module.get_bin_path('hg', True)
if dest is not None:
hgrc = os.path.join(dest, '.hg/hgrc')
# initial states
before = ''
changed = False
cleaned = False
if not dest and (clone or update):
module.fail_json(msg="the destination directory must be specified unless clone=no and update=no")
hg = Hg(module, dest, repo, revision, hg_path)
# If there is no hgrc file, then assume repo is absent
# and perform clone. Otherwise, perform pull and update.
if not clone and not update:
out = hg.get_remote_revision()
module.exit_json(after=out, changed=False)
if not os.path.exists(hgrc):
if clone:
(rc, out, err) = hg.clone()
if rc != 0:
module.fail_json(msg=err)
else:
module.exit_json(changed=False)
elif not update:
# Just return having found a repo already in the dest path
before = hg.get_revision()
elif hg.at_revision:
# no update needed, don't pull
before = hg.get_revision()
# but force and purge if desired
cleaned = hg.cleanup(force, purge)
else:
# get the current state before doing pulling
before = hg.get_revision()
# can perform force and purge
cleaned = hg.cleanup(force, purge)
(rc, out, err) = hg.pull()
if rc != 0:
module.fail_json(msg=err)
(rc, out, err) = hg.update()
if rc != 0:
module.fail_json(msg=err)
after = hg.get_revision()
if before != after or cleaned:
changed = True
module.exit_json(before=before, after=after, changed=changed, cleaned=cleaned)
if __name__ == '__main__':
main()