diff --git a/source_control/gitlab_project.py b/source_control/gitlab_project.py new file mode 100644 index 00000000000..602b9e832d7 --- /dev/null +++ b/source_control/gitlab_project.py @@ -0,0 +1,397 @@ +#!/usr/bin/python +# (c) 2015, Werner Dijkerman (ikben@werner-dijkerman.nl) +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +DOCUMENTATION = ''' +--- +module: gitlab_project +short_description: Creates/updates/deletes Gitlab Projects +description: + - When the project does not exists 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. +version_added: "2.1" +author: "Werner Dijkerman (@dj-wasabi)" +requirements: + - pyapi-gitlab python module +options: + server_url: + description: + - Url of Gitlab server, with protocol (http or https). + required: true + validate_certs: + description: + - When using https if SSL certificate needs to be verified. + required: false + default: true + aliases: + - verify_ssl + login_user: + description: + - Gitlab user name. + required: false + default: null + login_password: + description: + - Gitlab password for login_user + required: false + default: null + login_token: + description: + - Gitlab token for logging in. + required: false + default: null + group: + description: + - The name of the group of which this projects belongs to. + - When not provided, project will belong to user which is configured in 'login_user' or 'login_token' + - When provided with username, project will be created for this user. 'login_user' or 'login_token' needs admin rights. + required: false + default: null + name: + description: + - The name of the project + required: true + path: + description: + - The path of the project you want to create, this will be server_url//path + - If not supplied, name will be used. + required: false + default: null + description: + description: + - An description for the project. + required: false + default: null + issues_enabled: + description: + - Whether you want to create issues or not. + - Possible values are true and false. + required: false + default: true + merge_requests_enabled: + description: + - If merge requests can be made or not. + - Possible values are true and false. + required: false + default: true + wiki_enabled: + description: + - If an wiki for this project should be available or not. + - Possible values are true and false. + required: false + default: true + snippets_enabled: + description: + - If creating snippets should be available or not. + - Possible values are true and false. + required: false + default: true + public: + description: + - If the project is public available or not. + - Setting this to true is same as setting visibility_level to 20. + - Possible values are true and false. + required: false + default: false + visibility_level: + description: + - Private. visibility_level is 0. Project access must be granted explicitly for each user. + - Internal. visibility_level is 10. The project can be cloned by any logged in user. + - Public. visibility_level is 20. The project can be cloned without any authentication. + - Possible values are 0, 10 and 20. + required: false + default: 0 + import_url: + description: + - Git repository which will me imported into gitlab. + - Gitlab server needs read access to this git repository. + required: false + default: false + state: + description: + - create or delete project. + - Possible values are present and absent. + required: false + default: "present" + choices: ["present", "absent"] +''' + +EXAMPLES = ''' +- name: "Delete Gitlab Project" + local_action: gitlab_project + server_url="http://gitlab.dj-wasabi.local" + validate_certs=false + login_token="WnUzDsxjy8230-Dy_k" + name=my_first_project + state=absent + +- name: "Create Gitlab Project in group Ansible" + local_action: gitlab_project + server_url="https://gitlab.dj-wasabi.local" + validate_certs=true + login_user=dj-wasabi + login_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 +''' + +RETURN = '''# ''' + +try: + import gitlab + HAS_GITLAB_PACKAGE = True +except: + HAS_GITLAB_PACKAGE = False + + +class GitLabProject(object): + def __init__(self, module, git): + self._module = module + self._gitlab = git + + def createOrUpdateProject(self, project_exists, group_name, import_url, arguments): + is_user = False + group_id = self.getGroupId(group_name) + if not group_id: + group_id = self.getUserId(group_name) + is_user = True + + if project_exists: + # Edit project + return self.updateProject(group_name, arguments) + else: + # Create project + if self._module.check_mode: + self._module.exit_json(changed=True) + return self.createProject(is_user, group_id, import_url, arguments) + + def createProject(self, is_user, user_id, import_url, arguments): + if is_user: + return self._gitlab.createprojectuser(user_id=user_id, import_url=import_url, **arguments) + else: + group_id = user_id + return self._gitlab.createproject(namespace_id=group_id, import_url=import_url, **arguments) + + def deleteProject(self, group_name, project_name): + if self.existsGroup(group_name): + project_owner = group_name + else: + project_owner = self._gitlab.currentuser()['username'] + + search_results = self._gitlab.searchproject(search=project_name) + for result in search_results: + owner = result['namespace']['name'] + if owner == project_owner: + return self._gitlab.deleteproject(result['id']) + + def existsProject(self, group_name, project_name): + if self.existsGroup(group_name): + project_owner = group_name + else: + project_owner = self._gitlab.currentuser()['username'] + + search_results = self._gitlab.searchproject(search=project_name) + for result in search_results: + owner = result['namespace']['name'] + if owner == project_owner: + return True + return False + + def existsGroup(self, group_name): + if group_name is not None: + # Find the group, if group not exists we try for user + for group in self._gitlab.getall(self._gitlab.getgroups): + if group['name'] == group_name: + return True + + user_name = group_name + user_data = self._gitlab.getusers(search=user_name) + for data in user_data: + if 'id' in user_data: + return True + return False + + def getGroupId(self, group_name): + if group_name is not None: + # Find the group, if group not exists we try for user + for group in self._gitlab.getall(self._gitlab.getgroups): + if group['name'] == group_name: + return group['id'] + + def getProjectId(self, group_name, project_name): + if self.existsGroup(group_name): + project_owner = group_name + else: + project_owner = self._gitlab.currentuser()['username'] + + search_results = self._gitlab.searchproject(search=project_name) + for result in search_results: + owner = result['namespace']['name'] + if owner == project_owner: + return result['id'] + + def getUserId(self, user_name): + user_data = self._gitlab.getusers(search=user_name) + + for data in user_data: + if 'id' in data: + return data['id'] + return self._gitlab.currentuser()['id'] + + def to_bool(self, value): + if value: + return 1 + else: + return 0 + + def updateProject(self, group_name, arguments): + project_changed = False + project_name = arguments['name'] + project_id = self.getProjectId(group_name, project_name) + project_data = self._gitlab.getproject(project_id=project_id) + + for arg_key, arg_value in arguments.items(): + project_data_value = project_data[arg_key] + + if isinstance(project_data_value, bool) or project_data_value is None: + to_bool = self.to_bool(project_data_value) + if to_bool != arg_value: + project_changed = True + continue + else: + if project_data_value != arg_value: + project_changed = True + + if project_changed: + if self._module.check_mode: + self._module.exit_json(changed=True) + return self._gitlab.editproject(project_id=project_id, **arguments) + else: + return False + + +def main(): + module = AnsibleModule( + argument_spec=dict( + server_url=dict(required=True), + validate_certs=dict(required=False, default=True, type=bool, aliases=['verify_ssl']), + login_user=dict(required=False, no_log=True), + login_password=dict(required=False, no_log=True), + login_token=dict(required=False, no_log=True), + group=dict(required=False), + name=dict(required=True), + path=dict(required=False), + description=dict(required=False), + issues_enabled=dict(default=True, type=bool), + merge_requests_enabled=dict(default=True, type=bool), + wiki_enabled=dict(default=True, type=bool), + snippets_enabled=dict(default=True, type=bool), + public=dict(default=False, type=bool), + visibility_level=dict(default="0", choices=["0", "10", "20"]), + import_url=dict(required=False), + state=dict(default="present", choices=["present", 'absent']), + ), + supports_check_mode=True + ) + + if not HAS_GITLAB_PACKAGE: + module.fail_json(msg="Missing required gitlab module (check docs or install with: pip install pyapi-gitlab") + + server_url = module.params['server_url'] + verify_ssl = module.params['validate_certs'] + login_user = module.params['login_user'] + login_password = module.params['login_password'] + login_token = module.params['login_token'] + group_name = module.params['group'] + project_name = module.params['name'] + project_path = module.params['path'] + 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'] + public = module.params['public'] + visibility_level = module.params['visibility_level'] + import_url = module.params['import_url'] + state = module.params['state'] + + # We need both login_user and login_password or login_token, otherwise we fail. + if login_user is not None and login_password is not None: + use_credentials = True + elif login_token is not None: + use_credentials = False + else: + module.fail_json(msg="No login credentials are given. Use login_user with login_password, or login_token") + + # Set project_path to project_name if it is empty. + if project_path is None: + project_path = project_name.replace(" ", "_") + + # Gitlab API makes no difference between upper and lower cases, so we lower them. + project_name = project_name.lower() + project_path = project_path.lower() + if group_name is not None: + group_name = group_name.lower() + + # Lets make an connection to the Gitlab server_url, with either login_user and login_password + # or with login_token + try: + if use_credentials: + git = gitlab.Gitlab(host=server_url) + git.login(user=login_user, password=login_password) + else: + git = gitlab.Gitlab(server_url, token=login_token, verify_ssl=verify_ssl) + except Exception, e: + module.fail_json(msg="Failed to connect to Gitlab server: %s " % e) + + # Validate if project exists and take action based on "state" + project = GitLabProject(module, git) + project_exists = project.existsProject(group_name, project_name) + + # Creating the project dict + arguments = {"name": project_name, + "path": project_path, + "description": description, + "issues_enabled": project.to_bool(issues_enabled), + "merge_requests_enabled": project.to_bool(merge_requests_enabled), + "wiki_enabled": project.to_bool(wiki_enabled), + "snippets_enabled": project.to_bool(snippets_enabled), + "public": project.to_bool(public), + "visibility_level": int(visibility_level)} + + if project_exists and state == "absent": + project.deleteProject(group_name, project_name) + module.exit_json(changed=True, result="Successfully deleted project %s" % project_name) + else: + if state == "absent": + module.exit_json(changed=False, result="Project deleted or does not exists") + else: + if project.createOrUpdateProject(project_exists, group_name, import_url, arguments): + module.exit_json(changed=True, result="Successfully created or updated the project %s" % project_name) + else: + module.exit_json(changed=False) + +from ansible.module_utils.basic import * + +if __name__ == '__main__': + main()