diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index bd0afac294..251bb71bff 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -674,6 +674,8 @@ files: maintainers: pixslx $modules/gitlab_project_access_token.py: maintainers: pixslx + $modules/gitlab_project_approvals.py: + maintainers: masa-orca $modules/grove.py: maintainers: zimbatm $modules/gunicorn.py: diff --git a/plugins/modules/gitlab_project_approvals.py b/plugins/modules/gitlab_project_approvals.py new file mode 100644 index 0000000000..164e77422d --- /dev/null +++ b/plugins/modules/gitlab_project_approvals.py @@ -0,0 +1,208 @@ +#!/usr/bin/python + +# Copyright (c) 2026, Masaru Onodera (@masa-orca) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import annotations + +DOCUMENTATION = r""" +module: gitlab_project_approvals +short_description: Manage project-level merge request approvals settings on GitLab Server +version_added: 13.1.0 +description: + - This module allows to manage project-level merge request approval configurations. +author: + - "Masaru Onodera (@masa-orca)" +requirements: + - python-gitlab python module +extends_documentation_fragment: + - community.general._auth_basic + - community.general._gitlab + - community.general._attributes + +attributes: + check_mode: + support: full + diff_mode: + support: none + +options: + project: + description: + - The name or full path of the GitLab project. + required: true + type: str + + approvals_before_merge: + description: + - The number of approvals required before a merge request can be merged. + - Deprecated in GitLab 12.3. + type: int + + reset_approvals_on_push: + description: + - Reset approvals on new commit. + type: bool + + disable_overriding_approvers_per_merge_request: + description: + - Disable overriding approvers per merge request. + type: bool + + merge_requests_author_approval: + description: + - Allow authors to self-approve merge requests. + type: bool + + merge_requests_disable_committers_approval: + description: + - Prevent committers from approving merge requests. + type: bool + + selective_code_owner_removals: + description: + - Reset approvals from Code Owners if their files are changed. + type: bool + + require_password_to_approve: + description: + - Require password reauthentication to approve a merge request. + - Deprecated in GitLab 16.9. + type: bool + + require_reauthentication_to_approve: + description: + - Require user reauthentication to approve a merge request. + type: bool +""" + +EXAMPLES = r""" +- name: Configure GitLab Project approval settings + community.general.gitlab_project_approvals: + api_url: https://gitlab.example.com/ + api_token: "{{ api_token }}" + project: my_group/my_project + reset_approvals_on_push: true + merge_requests_author_approval: false + merge_requests_disable_committers_approval: true +""" + +RETURN = r""" +project_approvals: + description: The updated GitLab project approval settings. + returned: always + type: dict + sample: + approvals_before_merge: 2 + reset_approvals_on_push: true + disable_overriding_approvers_per_merge_request: false + merge_requests_author_approval: false + merge_requests_disable_committers_approval: true +""" + + +from ansible.module_utils.api import basic_auth_argument_spec +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.community.general.plugins.module_utils._gitlab import ( + auth_argument_spec, + find_project, + gitlab_authentication, +) + + +class GitlabProjectApprovals: + def __init__(self, module, gitlab_instance): + self._module = module + self._gitlab = gitlab_instance + self.project = self.get_project(module.params["project"]) + + def get_project(self, project_name): + project = find_project(self._gitlab, project_name) + if project is None: + self._module.fail_json(msg=f"Project {project_name} not found or insufficient permissions.") + return project + + def update_approval_settings(self, options): + changed = False + try: + approval_settings = self.project.approvals.get() + except Exception as e: + self._module.fail_json(msg=f"Failed to get project approval settings: {e}") + + # Check which options differ from current settings and apply them + for key, value in options.items(): + if value is not None: + current_value = getattr(approval_settings, key, None) + if current_value != value: + setattr(approval_settings, key, value) + changed = True + + if changed: + if not self._module.check_mode: + try: + approval_settings.save() + except Exception as e: + self._module.fail_json(msg=f"Failed to update project approval settings: {e}") + + return changed, approval_settings.attributes + + +def main(): + argument_spec = basic_auth_argument_spec() + argument_spec.update(auth_argument_spec()) + argument_spec.update( + dict( + project=dict(type="str", required=True), + approvals_before_merge=dict(type="int"), + reset_approvals_on_push=dict(type="bool"), + disable_overriding_approvers_per_merge_request=dict(type="bool"), + merge_requests_author_approval=dict(type="bool"), + merge_requests_disable_committers_approval=dict(type="bool"), + selective_code_owner_removals=dict(type="bool"), + require_password_to_approve=dict(type="bool"), + require_reauthentication_to_approve=dict(type="bool"), + ) + ) + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[ + ["api_username", "api_token"], + ["api_username", "api_oauth_token"], + ["api_username", "api_job_token"], + ["api_token", "api_oauth_token"], + ["api_token", "api_job_token"], + ], + required_together=[ + ["api_username", "api_password"], + ], + required_one_of=[["api_username", "api_token", "api_oauth_token", "api_job_token"]], + supports_check_mode=True, + ) + + gitlab_instance = gitlab_authentication(module) + + gitlab_project_approvals = GitlabProjectApprovals(module, gitlab_instance) + + options = { + "approvals_before_merge": module.params["approvals_before_merge"], + "reset_approvals_on_push": module.params["reset_approvals_on_push"], + "disable_overriding_approvers_per_merge_request": module.params[ + "disable_overriding_approvers_per_merge_request" + ], + "merge_requests_author_approval": module.params["merge_requests_author_approval"], + "merge_requests_disable_committers_approval": module.params["merge_requests_disable_committers_approval"], + "selective_code_owner_removals": module.params["selective_code_owner_removals"], + "require_password_to_approve": module.params["require_password_to_approve"], + "require_reauthentication_to_approve": module.params["require_reauthentication_to_approve"], + } + + changed, approval_attrs = gitlab_project_approvals.update_approval_settings(options) + + module.exit_json(changed=changed, project_approvals=approval_attrs) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/gitlab_project_approvals/defaults/main.yml b/tests/integration/targets/gitlab_project_approvals/defaults/main.yml new file mode 100644 index 0000000000..5234c106fd --- /dev/null +++ b/tests/integration/targets/gitlab_project_approvals/defaults/main.yml @@ -0,0 +1,8 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +gitlab_api_token: glpat-XXXXXXXXXXXXXXXXXXXX +gitlab_api_url: https://gitlab.com +gitlab_project_name: ansible_test_project diff --git a/tests/integration/targets/gitlab_project_approvals/tasks/main.yml b/tests/integration/targets/gitlab_project_approvals/tasks/main.yml new file mode 100644 index 0000000000..01af46f557 --- /dev/null +++ b/tests/integration/targets/gitlab_project_approvals/tasks/main.yml @@ -0,0 +1,74 @@ +--- +#################################################################### +# WARNING: These are designed specifically for Ansible tests # +# and should not be used as examples of how to write Ansible roles # +#################################################################### + +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Install required libs + pip: + name: python-gitlab + state: present + +- name: Set project approval settings + gitlab_project_approvals: + api_url: "{{ gitlab_api_url }}" + validate_certs: false + api_token: "{{ gitlab_api_token }}" + project: "{{ gitlab_project_name }}" + approvals_before_merge: 2 + reset_approvals_on_push: false + disable_overriding_approvers_per_merge_request: true + merge_requests_author_approval: true + merge_requests_disable_committers_approval: true + selective_code_owner_removals: true + require_password_to_approve: true + require_reauthentication_to_approve: true + register: approval_result + +- assert: + that: + - approval_result is changed + +- name: Re-run project approval for idempotency + gitlab_project_approvals: + api_url: "{{ gitlab_api_url }}" + validate_certs: false + api_token: "{{ gitlab_api_token }}" + project: "{{ gitlab_project_name }}" + approvals_before_merge: 2 + reset_approvals_on_push: false + disable_overriding_approvers_per_merge_request: true + merge_requests_author_approval: true + merge_requests_disable_committers_approval: true + selective_code_owner_removals: true + require_password_to_approve: true + require_reauthentication_to_approve: true + register: approval_result_idem + +- assert: + that: + - approval_result_idem is not changed + +- name: Reset project approval settings + gitlab_project_approvals: + api_url: "{{ gitlab_api_url }}" + validate_certs: false + api_token: "{{ gitlab_api_token }}" + project: "{{ gitlab_project_name }}" + approvals_before_merge: 0 + reset_approvals_on_push: true + disable_overriding_approvers_per_merge_request: false + merge_requests_author_approval: false + merge_requests_disable_committers_approval: false + selective_code_owner_removals: false + require_password_to_approve: false + require_reauthentication_to_approve: false + register: approval_result + +- assert: + that: + - approval_result is changed