Skip to content

Commit

Permalink
refactor: migrate branches configuration feature to use python-gitlab…
Browse files Browse the repository at this point in the history
… library

closes #625
  • Loading branch information
TimKnight-Opencast committed Mar 14, 2024
1 parent 17893a2 commit 1a8d178
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 105 deletions.
7 changes: 5 additions & 2 deletions gitlabform/processors/project/files_processor.py
Expand Up @@ -171,6 +171,7 @@ def modify_file_dealing_with_branch_protection(
):
# perhaps your user permissions are ok to just perform this operation regardless
# of the branch protection...
project = self.gl.projects.get(project_and_group)

try:
self.just_modify_file(
Expand All @@ -189,7 +190,8 @@ def modify_file_dealing_with_branch_protection(
debug(
f"> Temporarily unprotecting the branch to {operation} a file in it..."
)
self.branch_protector.unprotect_branch(project_and_group, branch)

self.branch_protector.unprotect_branch(project, branch)
else:
fatal(
f"Operation {operation} on file {file} in branch {branch} not permitted,"
Expand All @@ -212,9 +214,10 @@ def modify_file_dealing_with_branch_protection(
finally:
# ...and protect the branch again after the operation
if configuration.get("branches|" + branch + "|protected"):
branch_configuration = configuration["branches"][branch]
debug("> Protecting the branch again.")
self.branch_protector.protect_branch(
project_and_group, configuration, branch
project, branch_configuration, branch
)

else:
Expand Down
206 changes: 103 additions & 103 deletions gitlabform/processors/util/branch_protector.py
@@ -1,9 +1,10 @@
from logging import debug
from cli_ui import warning, fatal
from gitlab import Gitlab, GitlabGetError, GitlabDeleteError
from gitlab.v4.objects import Project, ProjectProtectedBranch

from gitlabform.constants import EXIT_PROCESSING_ERROR, EXIT_INVALID_INPUT
from gitlabform.gitlab import GitLab
from gitlabform.gitlab.core import NotFoundException
from gitlabform.gitlab import GitLab, GitlabWrapper


class BranchProtector:
Expand All @@ -18,74 +19,56 @@ class BranchProtector:
]

def __init__(self, gitlab: GitLab, strict: bool):
self.gitlab = gitlab
self.strict = strict
if gitlab:
self.gl: Gitlab = GitlabWrapper(gitlab)._gitlab

def apply_branch_protection_configuration(
self, project_and_group, configuration, branch
):
try:
requested_configuration = configuration["branches"][branch]

if requested_configuration.get("protected"):
self.protect_branch(project_and_group, configuration, branch)
else:
self.unprotect_branch(project_and_group, branch)
requested_configuration = configuration["branches"][branch]
project = self.gl.projects.get(project_and_group)

except NotFoundException:
message = f"Branch '{branch}' not found when trying to set it as protected/unprotected!"
if self.strict:
fatal(
message,
exit_code=EXIT_PROCESSING_ERROR,
)
else:
warning(message)
if requested_configuration.get("protected"):
self.protect_branch(project, requested_configuration, branch)
else:
self.unprotect_branch(project, branch)

def protect_branch(self, project_and_group, configuration, branch):
try:
requested_configuration = configuration["branches"][branch]
def protect_branch(self, project: Project, branch_configuration, branch):
self.validate_branch_protection_config(project, branch_configuration, branch)

self.validate_branch_protection_config(
project_and_group, requested_configuration, branch
if any(
extra_key in branch_configuration for extra_key in self.extra_param_keys
):
for extra_param_key in self.extra_param_keys:

Check warning on line 43 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L43

Added line #L43 was not covered by tests
# check if an extra_param is in config, and it contains the user parameter
if extra_param_key in branch_configuration and any(

Check warning on line 45 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L45

Added line #L45 was not covered by tests
"user" in d for d in branch_configuration[extra_param_key]
):
for extra_config in branch_configuration[extra_param_key]:

Check warning on line 48 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L48

Added line #L48 was not covered by tests
# loop over the array of extra param and get the user_id related to user
if "user" in extra_config.keys():
user_id = self._get_user_id_by_name(

Check warning on line 51 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L50-L51

Added lines #L50 - L51 were not covered by tests
extra_config.pop("user")
)
extra_config["user_id"] = user_id

Check warning on line 54 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L54

Added line #L54 was not covered by tests

if self.configuration_update_needed(branch_configuration, project, branch):
self.do_protect_branch(branch_configuration, project, branch)
else:
debug(
"Skipping setting branch '%s' protection configuration because it's already as requested.",
branch,
)

if any(
extra_key in requested_configuration
for extra_key in self.extra_param_keys
):
for extra_param_key in self.extra_param_keys:
# check if an extra_param is in config, and it contains the user parameter
if extra_param_key in requested_configuration and any(
"user" in d for d in requested_configuration[extra_param_key]
):
for extra_config in requested_configuration[extra_param_key]:
# loop over the array of extra param and get the user_id related to user
if "user" in extra_config.keys():
user_id = self.gitlab._get_user_id(
extra_config.pop("user")
)
extra_config["user_id"] = user_id

if self.configuration_update_needed(
requested_configuration, project_and_group, branch
):
self.do_protect_branch(
requested_configuration, project_and_group, branch
)
else:
debug(
"Skipping setting branch '%s' protection configuration because it's already as requested.",
branch,
)

if "code_owner_approval_required" in requested_configuration:
self.set_code_owner_approval_required(
requested_configuration, project_and_group, branch
)
if "code_owner_approval_required" in branch_configuration:
self.set_code_owner_approval_required(branch_configuration, project, branch)

Check warning on line 65 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L65

Added line #L65 was not covered by tests

except NotFoundException:
message = f"Branch '{branch}' not found when trying to set it as protected/unprotected!"
def unprotect_branch(self, project: Project, branch):
try:
project.branches.get(branch)
except GitlabGetError:
message = f"Branch '{branch}' could not be found in project '{project.path_with_namespace}!"

Check warning on line 71 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L70-L71

Added lines #L70 - L71 were not covered by tests
if self.strict:
fatal(
message,
Expand All @@ -94,23 +77,15 @@ def protect_branch(self, project_and_group, configuration, branch):
else:
warning(message)

def unprotect_branch(self, project_and_group, branch):
try:
debug("Setting branch '%s' as unprotected", branch)
self.gitlab.unprotect_branch(project_and_group, branch)

except NotFoundException:
message = f"Branch '{branch}' not found when trying to set it as protected/unprotected!"
if self.strict:
fatal(
message,
exit_code=EXIT_PROCESSING_ERROR,
)
else:
warning(message)
project.protectedbranches.delete(branch)
except GitlabDeleteError:
message = f"Branch '{branch}' could not be set to unprotected!"
warning(message)

def validate_branch_protection_config(
self, project_and_group, requested_configuration, branch
self, project: Project, requested_configuration, branch
):
# for the new API any key needs to be defined...
if any(
Expand All @@ -120,30 +95,33 @@ def validate_branch_protection_config(
return
else:
fatal(
f"Invalid configuration for protecting branches in project '{project_and_group}',"
f"Invalid configuration for protecting branches in project '{project.path_with_namespace}',"
f" branch '{branch}' - missing keys. Required is any of these: "
f"{self.new_api_keys + self.extra_param_keys}",
exit_code=EXIT_INVALID_INPUT,
)

def do_protect_branch(self, requested_configuration, project_and_group, branch):
def do_protect_branch(self, requested_configuration, project: Project, branch):
debug("Setting branch '%s' access level", branch)

# Protected Branches API is one of those that do not support editing entities
# (PUT is not documented for it, at least). so you need to delete existing
# branch protection (DELETE) and recreate it (POST) to perform an update
# (otherwise you get HTTP 409 "Protected branch 'foo' already exists")
self.gitlab.unprotect_branch(project_and_group, branch)
try:
# Protected Branches API is one of those that do not support editing entities
# (PUT is not documented for it, at least). so you need to delete existing
# branch protection (DELETE) and recreate it (POST) to perform an update
# (otherwise you get HTTP 409 "Protected branch 'foo' already exists")
project.protectedbranches.delete(branch)
except GitlabDeleteError:
debug("Branch already unprotected")

# replace in our config our custom "user" and "group" entries with supported by
# the Protected Branches API "user_id" and "group_id"
for extra_param_key in self.extra_param_keys:
for element in requested_configuration.get(extra_param_key, []):
if "user" in element.keys():
user_id = self.gitlab._get_user_id(element.pop("user"))
user_id = self._get_user_id_by_name(element.pop("user"))

Check warning on line 121 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L121

Added line #L121 was not covered by tests
element["user_id"] = user_id
elif "group" in element.keys():
group_id = self.gitlab._get_group_id(element.pop("group"))
group_id = self._get_group_id_by_name(element.pop("group"))

Check warning on line 124 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L124

Added line #L124 was not covered by tests
element["group_id"] = group_id

protect_rules = {
Expand All @@ -152,30 +130,45 @@ def do_protect_branch(self, requested_configuration, project_and_group, branch):
if key != "protected"
}

self.gitlab.protect_branch(
project_and_group,
branch,
protect_rules,
)
project.protectedbranches.create({"name": branch, **protect_rules})

def set_code_owner_approval_required(
self, requested_configuration, project_and_group, branch
self, requested_configuration, project: Project, branch: str
):
debug(
"Setting branch '%s' \"code owner approval required\" option",
branch,
)
self.gitlab.set_branch_code_owner_approval_required(
project_and_group,
branch,
requested_configuration["code_owner_approval_required"],
)

code_owner_approval_required = requested_configuration[

Check warning on line 143 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L143

Added line #L143 was not covered by tests
"code_owner_approval_required"
]

try:
protected_branch: ProjectProtectedBranch = project.protectedbranches.get(

Check warning on line 148 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L147-L148

Added lines #L147 - L148 were not covered by tests
branch
)

if protected_branch:
protected_branch.code_owner_approval_required = (

Check warning on line 153 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L152-L153

Added lines #L152 - L153 were not covered by tests
code_owner_approval_required
)
protected_branch.save()
except GitlabGetError:
message = f"Protected Branch '{branch}' not found when trying to set code_owner_approval_required!"
if self.strict:
fatal(

Check warning on line 160 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L156-L160

Added lines #L156 - L160 were not covered by tests
message,
exit_code=EXIT_PROCESSING_ERROR,
)
else:
warning(message)

Check warning on line 165 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L165

Added line #L165 was not covered by tests

def configuration_update_needed(
self, requested_configuration, project_and_group, branch
self, requested_configuration, project: Project, branch
):
# get current configuration of branch access level
current = self.get_current_branch_configuration(project_and_group, branch)
current = self.get_current_branch_configuration(project, branch)

# get requested configuration of branch access level
requested = self.get_requested_branch_configuration(requested_configuration)
Expand All @@ -184,7 +177,7 @@ def configuration_update_needed(
debug("Requested branch '%s' access levels: %s", branch, requested)
return current != requested

def get_current_branch_configuration(self, project_and_group_name, branch):
def get_current_branch_configuration(self, project: Project, branch):
# from a response for a request to Protected Branches API GET request
# (https://docs.gitlab.com/ee/api/protected_branches.html#get-a-single-protected-branch-or-wildcard-protected-branch)
# like:
Expand Down Expand Up @@ -212,10 +205,8 @@ def get_current_branch_configuration(self, project_and_group_name, branch):
# the other extra settings that can be applied using Protected Branches API POST request
#
try:
protected_branches_response = self.gitlab.get_branch_access_levels(
project_and_group_name, branch
)

protected_branch = project.protectedbranches.get(branch)
protected_branches_response = protected_branch.attributes
return (
*(
self.get_current_permissions(protected_branches_response, "push")
Expand All @@ -231,10 +222,11 @@ def get_current_branch_configuration(self, project_and_group_name, branch):
protected_branches_response.get("allow_force_push"),
# code_owner_approval_required has to use PATCH request, see set_code_owner_approval_required()
)
except NotFoundException:
except GitlabGetError:
return tuple([None] * 10) # = 3 * 3 + 1

def get_current_permissions(self, protected_branches_response, action):
@staticmethod
def get_current_permissions(protected_branches_response: dict, action):
# from a response for a request to Protected Branches API GET request
# (https://docs.gitlab.com/ee/api/protected_branches.html#get-a-single-protected-branch-or-wildcard-protected-branch)
# like:
Expand Down Expand Up @@ -344,10 +336,18 @@ def get_requested_permissions(self, requested_configuration, action):
elif "user_id" in config:
user_ids.add(config["user_id"])
elif "user" in config:
user_ids.add(self.gitlab._get_user_id(config["user"]))
user_id = self._get_user_id_by_name(config["user"])
user_ids.add(user_id)

Check warning on line 340 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L339-L340

Added lines #L339 - L340 were not covered by tests
elif "group_id" in config:
group_ids.add(config["group_id"])
elif "group" in config:
group_ids.add(self.gitlab._get_group_id(config["group"]))
group_id = self._get_group_id_by_name(config["group"])
group_ids.add(group_id)

Check warning on line 345 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L344-L345

Added lines #L344 - L345 were not covered by tests

return sorted(levels), sorted(user_ids), sorted(group_ids)

def _get_group_id_by_name(self, group_name):
return self.gl.groups.list(search=group_name).id

Check warning on line 350 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L350

Added line #L350 was not covered by tests

def _get_user_id_by_name(self, username):
return self.gl.users.username(username=username)[0].id

Check warning on line 353 in gitlabform/processors/util/branch_protector.py

View check run for this annotation

Codecov / codecov/patch

gitlabform/processors/util/branch_protector.py#L353

Added line #L353 was not covered by tests

0 comments on commit 1a8d178

Please sign in to comment.