mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-10-03 23:14:02 -07:00
gitlab_protected_branch: refactor, add `allow_force_push`, `code_owner_approval_required` (#10795)
* gitlab_protected_branch: fix typo
* gitlab_protected_branch: lump parameters into options dictionary
Hardcoding parameter lists gets repetitive. Refactor this module to use
an options dictionary like many other gitlab_* modules. This makes it
cleaner to add new options.
* gitlab_protected_branch: update when possible
Until now, the module deletes and re-creates the protected branch if any
change is detected. This makes sense for the access level parameters, as
these are not easily mutated after creation.
However, in order to add further options which _can_ easily be updated,
we should support updating by default, unless known-immutable parameters
are changing.
* gitlab_protected_branch: add `allow_force_push` option
* gitlab_protected_branch: add `code_owner_approval_required` option
* gitlab_protected_branch: add issues to changelog
* Update changelog.
---------
(cherry picked from commit f772bcda88
)
Co-authored-by: David Phillips <phillid@users.noreply.github.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
227 lines
8.1 KiB
Python
227 lines
8.1 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2021, Werner Dijkerman (ikben@werner-dijkerman.nl)
|
|
# 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 absolute_import, division, print_function
|
|
__metaclass__ = type
|
|
|
|
DOCUMENTATION = r"""
|
|
module: gitlab_protected_branch
|
|
short_description: Manage protection of existing branches
|
|
version_added: 3.4.0
|
|
description:
|
|
- (un)Marking existing branches for protection.
|
|
author:
|
|
- "Werner Dijkerman (@dj-wasabi)"
|
|
requirements:
|
|
- python-gitlab >= 2.3.0
|
|
extends_documentation_fragment:
|
|
- community.general.auth_basic
|
|
- community.general.gitlab
|
|
- community.general.attributes
|
|
|
|
attributes:
|
|
check_mode:
|
|
support: full
|
|
diff_mode:
|
|
support: none
|
|
|
|
options:
|
|
state:
|
|
description:
|
|
- Create or delete protected branch.
|
|
default: present
|
|
type: str
|
|
choices: ["present", "absent"]
|
|
project:
|
|
description:
|
|
- The path and name of the project.
|
|
required: true
|
|
type: str
|
|
name:
|
|
description:
|
|
- The name of the branch that needs to be protected.
|
|
- Can make use a wildcard character for like V(production/*) or just have V(main) or V(develop) as value.
|
|
required: true
|
|
type: str
|
|
merge_access_levels:
|
|
description:
|
|
- Access levels allowed to merge.
|
|
default: maintainer
|
|
type: str
|
|
choices: ["maintainer", "developer", "nobody"]
|
|
push_access_level:
|
|
description:
|
|
- Access levels allowed to push.
|
|
default: maintainer
|
|
type: str
|
|
choices: ["maintainer", "developer", "nobody"]
|
|
allow_force_push:
|
|
description:
|
|
- Whether or not to allow force pushes to the protected branch.
|
|
type: bool
|
|
version_added: '11.3.0'
|
|
code_owner_approval_required:
|
|
description:
|
|
- Whether or not to require code owner approval to push.
|
|
type: bool
|
|
version_added: '11.3.0'
|
|
"""
|
|
|
|
|
|
EXAMPLES = r"""
|
|
- name: Create protected branch on main
|
|
community.general.gitlab_protected_branch:
|
|
api_url: https://gitlab.com
|
|
api_token: secret_access_token
|
|
project: "dj-wasabi/collection.general"
|
|
name: main
|
|
merge_access_levels: maintainer
|
|
push_access_level: nobody
|
|
"""
|
|
|
|
RETURN = r"""
|
|
"""
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils.api import basic_auth_argument_spec
|
|
|
|
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
|
|
|
|
from ansible_collections.community.general.plugins.module_utils.gitlab import (
|
|
auth_argument_spec, gitlab_authentication, gitlab
|
|
)
|
|
|
|
|
|
class GitlabProtectedBranch(object):
|
|
|
|
def __init__(self, module, project, gitlab_instance):
|
|
self.repo = gitlab_instance
|
|
self._module = module
|
|
self.project = self.get_project(project)
|
|
self.ACCESS_LEVEL = {
|
|
'nobody': gitlab.const.NO_ACCESS,
|
|
'developer': gitlab.const.DEVELOPER_ACCESS,
|
|
'maintainer': gitlab.const.MAINTAINER_ACCESS
|
|
}
|
|
|
|
def get_project(self, project_name):
|
|
return self.repo.projects.get(project_name)
|
|
|
|
def protected_branch_exist(self, name):
|
|
try:
|
|
return self.project.protectedbranches.get(name)
|
|
except Exception as e:
|
|
return False
|
|
|
|
def create_or_update_protected_branch(self, name, options):
|
|
protected_branch_options = {
|
|
'name': name,
|
|
'allow_force_push': options['allow_force_push'],
|
|
'code_owner_approval_required': options['code_owner_approval_required'],
|
|
}
|
|
protected_branch = self.protected_branch_exist(name=name)
|
|
changed = False
|
|
if protected_branch and self.can_update(protected_branch, options):
|
|
for arg_key, arg_value in protected_branch_options.items():
|
|
if arg_value is not None:
|
|
if getattr(protected_branch, arg_key) != arg_value:
|
|
setattr(protected_branch, arg_key, arg_value)
|
|
changed = True
|
|
if changed and not self._module.check_mode:
|
|
protected_branch.save()
|
|
else:
|
|
# Set immutable options only on (re)creation
|
|
protected_branch_options['merge_access_level'] = options['merge_access_levels']
|
|
protected_branch_options['push_access_level'] = options['push_access_level']
|
|
if protected_branch:
|
|
# Exists, but couldn't update. So, delete first
|
|
self.delete_protected_branch(name)
|
|
if not self._module.check_mode:
|
|
self.project.protectedbranches.create(protected_branch_options)
|
|
changed = True
|
|
|
|
return changed
|
|
|
|
def can_update(self, protected_branch, options):
|
|
# these keys are not set on update the same way they are on creation
|
|
configured_merge = options['merge_access_levels']
|
|
configured_push = options['push_access_level']
|
|
current_merge = protected_branch.merge_access_levels[0]['access_level']
|
|
current_push = protected_branch.push_access_levels[0]['access_level']
|
|
return ((configured_merge is None or current_merge == configured_merge) and
|
|
(configured_push is None or current_push == configured_push))
|
|
|
|
def delete_protected_branch(self, name):
|
|
if self._module.check_mode:
|
|
return True
|
|
return self.project.protectedbranches.delete(name)
|
|
|
|
|
|
def main():
|
|
argument_spec = basic_auth_argument_spec()
|
|
argument_spec.update(auth_argument_spec())
|
|
argument_spec.update(
|
|
project=dict(type='str', required=True),
|
|
name=dict(type='str', required=True),
|
|
merge_access_levels=dict(type='str', default="maintainer", choices=["maintainer", "developer", "nobody"]),
|
|
push_access_level=dict(type='str', default="maintainer", choices=["maintainer", "developer", "nobody"]),
|
|
allow_force_push=dict(type='bool'),
|
|
code_owner_approval_required=dict(type='bool'),
|
|
state=dict(type='str', default="present", choices=["absent", "present"]),
|
|
)
|
|
|
|
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
|
|
)
|
|
|
|
# check prerequisites and connect to gitlab server
|
|
gitlab_instance = gitlab_authentication(module)
|
|
|
|
project = module.params['project']
|
|
name = module.params['name']
|
|
merge_access_levels = module.params['merge_access_levels']
|
|
push_access_level = module.params['push_access_level']
|
|
state = module.params['state']
|
|
|
|
gitlab_version = gitlab.__version__
|
|
if LooseVersion(gitlab_version) < LooseVersion('2.3.0'):
|
|
module.fail_json(msg="community.general.gitlab_protected_branch requires python-gitlab Python module >= 2.3.0 (installed version: [%s])."
|
|
" Please upgrade python-gitlab to version 2.3.0 or above." % gitlab_version)
|
|
|
|
this_gitlab = GitlabProtectedBranch(module=module, project=project, gitlab_instance=gitlab_instance)
|
|
|
|
p_branch = this_gitlab.protected_branch_exist(name=name)
|
|
options = {
|
|
"merge_access_levels": this_gitlab.ACCESS_LEVEL[merge_access_levels],
|
|
"push_access_level": this_gitlab.ACCESS_LEVEL[push_access_level],
|
|
"allow_force_push": module.params["allow_force_push"],
|
|
"code_owner_approval_required": module.params["code_owner_approval_required"],
|
|
}
|
|
if state == "present":
|
|
changed = this_gitlab.create_or_update_protected_branch(name, options)
|
|
module.exit_json(changed=changed, msg="Created or updated the protected branch.")
|
|
elif p_branch and state == "absent":
|
|
this_gitlab.delete_protected_branch(name=name)
|
|
module.exit_json(changed=True, msg="Deleted the protected branch.")
|
|
module.exit_json(changed=False, msg="No changes are needed.")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|