diff --git a/source_control/gitea_repository.py b/source_control/gitea_repository.py new file mode 100644 index 0000000..62e623f --- /dev/null +++ b/source_control/gitea_repository.py @@ -0,0 +1,267 @@ +import json +import requests + +ANSIBLE_METADATA = { + 'metadata_version': '1.1', + 'status': ['preview'], +} + +DOCUMENTATION = ''' +--- +''' + +EXAMPLES = ''' +''' + +RETURN = ''' +''' + +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()