385 lines
13 KiB
Python
385 lines
13 KiB
Python
import json
|
|
import requests
|
|
|
|
ANSIBLE_METADATA = {
|
|
'metadata_version': '1.1',
|
|
'status': ['preview'],
|
|
}
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: gitea_repository
|
|
|
|
short_description: Manage Gitea repositories
|
|
|
|
description:
|
|
- "Manage repositories (and deployment keys) in Gitea"
|
|
|
|
options:
|
|
state:
|
|
description:
|
|
- The desired state of the repository.
|
|
choices: ['present', 'absent']
|
|
default: present
|
|
required: no
|
|
name:
|
|
description:
|
|
- The name for your repository.
|
|
required: true
|
|
private:
|
|
description:
|
|
- Wether the repository is publically accessible
|
|
choices: ['yes', 'no']
|
|
default: yes
|
|
required: false
|
|
organization:
|
|
description:
|
|
- The organization account the repository belongs to.
|
|
One of organization and user must be set.
|
|
Only one of organization and user must be set.
|
|
required: false
|
|
user:
|
|
description:
|
|
- The user account the repository belongs to.
|
|
One of organization and user must be set.
|
|
Only one of organization and user must be set.
|
|
required: false
|
|
auto_init:
|
|
description:
|
|
- Wether to initialize the repository with default files.
|
|
Can only be given on creation.
|
|
required: false
|
|
choices: ['yes', 'no']
|
|
description:
|
|
description:
|
|
- The repositories description.
|
|
required: false
|
|
gitignores:
|
|
description:
|
|
- .gitignore file for the repository.
|
|
Can only be given on creation.
|
|
required: false
|
|
issue_labels:
|
|
description:
|
|
- Issue labels for the repository.
|
|
Can only be given on creation.
|
|
required: false
|
|
license:
|
|
description:
|
|
- license file for the repository.
|
|
Can only be given on creation.
|
|
required: false
|
|
readme:
|
|
description:
|
|
- README (in Markdown) for the repository.
|
|
Can only be given on creation.
|
|
required: false
|
|
deploy_key:
|
|
description:
|
|
- SSH deployment key that is to be set up for the repository
|
|
suboptions:
|
|
state:
|
|
description:
|
|
- The desired state of the deploy key.
|
|
choices: ['present', 'absent']
|
|
default: present
|
|
required: false
|
|
key:
|
|
description:
|
|
- The public key in SSH format (e.g. "ssh-rsa 12345....12345 identifier")
|
|
required: true
|
|
read_only:
|
|
description:
|
|
- Wether the deploy key should have read-only access to the repository.
|
|
choices: ['yes', 'no']
|
|
default: yes
|
|
title:
|
|
description:
|
|
- A title for identification of the supplied key
|
|
required: false
|
|
required: false
|
|
auth_token:
|
|
description:
|
|
- Authentification token for your gitea account
|
|
required: true
|
|
gitea_url:
|
|
description:
|
|
- Base URL of your gitea API instance (e.g. "https://git.zknt.org")
|
|
required: true
|
|
author:
|
|
- Chris Gebhardt <cg@zknt.org> (@hnrd)
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
gitea_repository:
|
|
auth_token: 1234...6789
|
|
gitea_url: https://git.example.com
|
|
name: 'testrepo'
|
|
organization: test123
|
|
state: present
|
|
deploy_key:
|
|
key: ssh-ed25519 AAAA...1234 jenkins
|
|
title: CI key
|
|
read_only: False
|
|
'''
|
|
|
|
RETURN = '''
|
|
return_code:
|
|
description: The HTTP return code from the Gitea API
|
|
type: int
|
|
returned: always
|
|
gitea_respone:
|
|
description: The JSON output message that Gitea returns
|
|
type: dict
|
|
'''
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
def run_module():
|
|
deploy_key_spec = dict(
|
|
state=dict(type='str', default='present', choices=['present', 'absent']),
|
|
key=dict(type='str', required=True),
|
|
read_only=dict(type='bool', default=True),
|
|
title=dict(type='str', default=""),
|
|
)
|
|
module_args = dict(
|
|
state=dict(type='str', default='present', choices=['present', 'absent']),
|
|
auth_token=dict(type='str', required=True),
|
|
gitea_url=dict(type='str', required=True),
|
|
|
|
organization=dict(type="str", default=None),
|
|
user=dict(type="str", default=None),
|
|
auto_init=dict(type="bool", default=False),
|
|
description=dict(type="str", default=""),
|
|
gitignores=dict(type="str", default=""),
|
|
issue_labels=dict(type="str", default=""),
|
|
license=dict(type="str", default=""),
|
|
name=dict(type="str", default=""),
|
|
private=dict(type="bool", default=True),
|
|
readme=dict(type="str", default=""),
|
|
|
|
### TODO support multiple keys
|
|
deploy_key=dict(type="dict", default=None, options=deploy_key_spec),
|
|
)
|
|
|
|
result = dict(
|
|
changed=False,
|
|
return_code=0,
|
|
gitea_response={},
|
|
)
|
|
|
|
module = AnsibleModule(
|
|
argument_spec=module_args,
|
|
supports_check_mode=True
|
|
)
|
|
|
|
if (
|
|
('organization' in module.params and module.params['organization']) and
|
|
('user' in module.params and module.params['user'])
|
|
):
|
|
module.fail_json(msg="Either organization *or*" \
|
|
"user must be set", **result)
|
|
|
|
gitea_url = module.params['gitea_url']
|
|
reponame = module.params['name']
|
|
headers = {
|
|
"Content-Type": "application/json",
|
|
"Authorization": "token {}".format(module.params['auth_token']),
|
|
}
|
|
|
|
user_req = requests.get(gitea_url + '/api/v1/user', headers=headers)
|
|
logged_in = user_req.json().get('username')
|
|
if not logged_in == module.params['user']:
|
|
headers['Sudo'] = module.params['user']
|
|
|
|
if module.check_mode:
|
|
module.exit_json(**result)
|
|
|
|
# Prepare repo data
|
|
new_data = {
|
|
"auto_init": module.params['auto_init'],
|
|
"description": module.params['description'],
|
|
"gitignores": module.params['gitignores'],
|
|
"issue_labels": module.params['issue_labels'],
|
|
"license": module.params['license'],
|
|
"name": reponame,
|
|
"private": module.params['private'],
|
|
"readme": module.params['readme'],
|
|
}
|
|
|
|
if 'organization' in module.params and module.params['organization']:
|
|
owner = module.params['organization']
|
|
else:
|
|
owner = module.params['user']
|
|
|
|
original_repo = requests.get(
|
|
gitea_url + '/api/v1/repos/{owner}/{repo}'.format(
|
|
owner=owner,
|
|
repo=reponame,
|
|
),
|
|
headers=headers,
|
|
)
|
|
|
|
if module.params['state'] == 'present':
|
|
if original_repo.status_code == 200:
|
|
result['return_code'] = 200
|
|
result['gitea_response'] = original_repo.json()
|
|
old_data = original_repo.json()
|
|
# check if repo needs to be patched
|
|
if (
|
|
new_data['description'] != old_data['description'] or
|
|
new_data['private'] != old_data['private']
|
|
):
|
|
new_data.pop('name')
|
|
new_data.pop('auto_init')
|
|
new_data.pop('gitignores')
|
|
new_data.pop('issue_labels')
|
|
new_data.pop('readme')
|
|
req_patch = requests.patch(
|
|
gitea_url + '/api/v1/repos/{owner}/{repo}'.format(
|
|
owner=module.params['organization'],
|
|
repo=reponame,
|
|
),
|
|
headers=headers,
|
|
data=json.dumps(new_data),
|
|
)
|
|
result['return_code'] = req_patch.status_code
|
|
result['gitea_response'] = req_patch.json()
|
|
result['changed'] = True
|
|
# all is fine
|
|
result['state'] = 'present'
|
|
|
|
elif original_repo.status_code == 404:
|
|
if (
|
|
'organization' in module.params and
|
|
module.params['organization']
|
|
):
|
|
create_path = '/api/v1/org/{org}/repos'.format(
|
|
org=owner,
|
|
)
|
|
else:
|
|
create_path = '/api/v1/user/repos'
|
|
create_req = requests.post(
|
|
gitea_url + create_path,
|
|
headers=headers,
|
|
data=json.dumps(new_data),
|
|
)
|
|
result['return_code'] = create_req.status_code
|
|
result['gitea_response'] = create_req.json()
|
|
if create_req.status_code != 201:
|
|
module.fail_json(msg="Creation failed", **result)
|
|
result['changed'] = True
|
|
result['state'] = 'present'
|
|
if 'deploy_key' in module.params:
|
|
deploy_key = module.params['deploy_key']
|
|
repokeys_req = requests.get(
|
|
gitea_url + '/api/v1/repos/{owner}/{repo}/keys'.format(
|
|
owner=owner,
|
|
repo=reponame,
|
|
),
|
|
headers=headers,
|
|
)
|
|
repokeys = repokeys_req.json()
|
|
key_id = None
|
|
repokey = None
|
|
for repokey in repokeys:
|
|
if (
|
|
' '.join(repokey.get('key', '').split()[:-1]) ==
|
|
' '.join(deploy_key.get('key', '').split()[:-1])
|
|
):
|
|
key_id = repokey.get('key_id')
|
|
break
|
|
|
|
if deploy_key['state'] == "present":
|
|
if key_id:
|
|
if (
|
|
repokey.get('title', "") != deploy_key['title'] or
|
|
repokey.get('read_only') !=
|
|
deploy_key['read_only'] or
|
|
repokey.get('key', "") != deploy_key['key']
|
|
):
|
|
# there is no PATCH for keys, so delete and re-create
|
|
keydelete_req = requests.delete(
|
|
gitea_url +
|
|
'/api/v1/repos/{owner}/{repo}/keys/{id}'.format(
|
|
owner=owner,
|
|
repo=reponame,
|
|
id=key_id,
|
|
),
|
|
headers=headers,
|
|
)
|
|
requests.post(
|
|
gitea_url +
|
|
'/api/v1/repos/{owner}/{repo}/keys'.format(
|
|
owner=owner,
|
|
repo=reponame,
|
|
),
|
|
headers=headers,
|
|
data=json.dumps({
|
|
"key": deploy_key['key'],
|
|
"read_only": deploy_key['read_only'],
|
|
"title": deploy_key['title'],
|
|
}),
|
|
)
|
|
result['changed'] = True
|
|
else:
|
|
requests.post(
|
|
gitea_url +
|
|
'/api/v1/repos/{owner}/{repo}/keys'.format(
|
|
owner=owner,
|
|
repo=reponame,
|
|
),
|
|
headers=headers,
|
|
data=json.dumps({
|
|
"key": deploy_key['key'],
|
|
"read_only": deploy_key['read_only'],
|
|
"title": deploy_key['title'],
|
|
}),
|
|
)
|
|
result['changed'] = True
|
|
elif deploy_key['state'] == "absent":
|
|
if key_id:
|
|
keydelete_req = requests.delete(
|
|
gitea_url +
|
|
'/api/v1/repos/{owner}/{repo}/keys/{id}'.format(
|
|
owner=owner,
|
|
repo=reponame,
|
|
id=key_id,
|
|
),
|
|
headers=headers,
|
|
)
|
|
if keydelete_req.status_code != 204:
|
|
# deletion failed
|
|
result['gitea_response'] = keydelete_req.json()
|
|
module.fail_json(msg="Deletion failed", **result)
|
|
result['changed'] = True
|
|
|
|
if module.params['state'] == 'absent':
|
|
if original_repo.status_code == 404:
|
|
result['return_code'] = 404
|
|
result['gitea_response'] = original_repo.json()
|
|
result['state'] = 'absent'
|
|
elif original_repo.status_code == 200:
|
|
# repo should be abenst and needs to be deleted
|
|
delete_req = requests.delete(
|
|
gitea_url + '/api/v1/repos/{owner}/{repo}'.format(
|
|
owner=owner,
|
|
repo=reponame,
|
|
),
|
|
headers=headers,
|
|
)
|
|
result['return_code'] = delete_req.status_code
|
|
if delete_req.status_code != 204:
|
|
# deletion failed
|
|
result['gitea_response'] = delete_req.json()
|
|
module.fail_json(msg="Deletion failed", **result)
|
|
result['changed'] = True
|
|
result['state'] = 'absent'
|
|
module.exit_json(**result)
|
|
|
|
def main():
|
|
run_module()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|