mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-06-03 06:49:10 -07:00
Initial commit
This commit is contained in:
commit
aebc1b03fd
4861 changed files with 812621 additions and 0 deletions
284
plugins/modules/source_control/bitbucket/bitbucket_access_key.py
Normal file
284
plugins/modules/source_control/bitbucket/bitbucket_access_key.py
Normal 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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
194
plugins/modules/source_control/bzr.py
Normal file
194
plugins/modules/source_control/bzr.py
Normal 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()
|
267
plugins/modules/source_control/git_config.py
Normal file
267
plugins/modules/source_control/git_config.py
Normal 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()
|
333
plugins/modules/source_control/github/github_deploy_key.py
Normal file
333
plugins/modules/source_control/github/github_deploy_key.py
Normal 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()
|
197
plugins/modules/source_control/github/github_hooks.py
Normal file
197
plugins/modules/source_control/github/github_hooks.py
Normal 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()
|
118
plugins/modules/source_control/github/github_issue.py
Normal file
118
plugins/modules/source_control/github/github_issue.py
Normal 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()
|
242
plugins/modules/source_control/github/github_key.py
Normal file
242
plugins/modules/source_control/github/github_key.py
Normal 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()
|
218
plugins/modules/source_control/github/github_release.py
Normal file
218
plugins/modules/source_control/github/github_release.py
Normal 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()
|
284
plugins/modules/source_control/github/github_webhook.py
Normal file
284
plugins/modules/source_control/github/github_webhook.py
Normal 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()
|
1
plugins/modules/source_control/github/github_webhook_facts.py
Symbolic link
1
plugins/modules/source_control/github/github_webhook_facts.py
Symbolic link
|
@ -0,0 +1 @@
|
|||
github_webhook_info.py
|
174
plugins/modules/source_control/github/github_webhook_info.py
Normal file
174
plugins/modules/source_control/github/github_webhook_info.py
Normal 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()
|
300
plugins/modules/source_control/gitlab/gitlab_deploy_key.py
Normal file
300
plugins/modules/source_control/gitlab/gitlab_deploy_key.py
Normal 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()
|
324
plugins/modules/source_control/gitlab/gitlab_group.py
Normal file
324
plugins/modules/source_control/gitlab/gitlab_group.py
Normal 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()
|
391
plugins/modules/source_control/gitlab/gitlab_hook.py
Normal file
391
plugins/modules/source_control/gitlab/gitlab_hook.py
Normal 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()
|
1
plugins/modules/source_control/gitlab/gitlab_hooks.py
Symbolic link
1
plugins/modules/source_control/gitlab/gitlab_hooks.py
Symbolic link
|
@ -0,0 +1 @@
|
|||
gitlab_hook.py
|
364
plugins/modules/source_control/gitlab/gitlab_project.py
Normal file
364
plugins/modules/source_control/gitlab/gitlab_project.py
Normal 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()
|
291
plugins/modules/source_control/gitlab/gitlab_project_variable.py
Normal file
291
plugins/modules/source_control/gitlab/gitlab_project_variable.py
Normal 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()
|
353
plugins/modules/source_control/gitlab/gitlab_runner.py
Normal file
353
plugins/modules/source_control/gitlab/gitlab_runner.py
Normal 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()
|
477
plugins/modules/source_control/gitlab/gitlab_user.py
Normal file
477
plugins/modules/source_control/gitlab/gitlab_user.py
Normal 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()
|
296
plugins/modules/source_control/hg.py
Normal file
296
plugins/modules/source_control/hg.py
Normal 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()
|
Loading…
Add table
Add a link
Reference in a new issue