gitlabform 5.0.2__tar.gz → 5.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gitlabform-5.0.2 → gitlabform-5.1.1}/PKG-INFO +9 -9
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/configuration/core.py +24 -12
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/core.py +5 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_hooks_processor.py +14 -3
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/branches_processor.py +178 -126
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform.egg-info/PKG-INFO +9 -9
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform.egg-info/requires.txt +8 -8
- {gitlabform-5.0.2 → gitlabform-5.1.1}/pyproject.toml +9 -9
- {gitlabform-5.0.2 → gitlabform-5.1.1}/LICENSE +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/README.md +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/__init__.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/configuration/__init__.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/configuration/common.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/configuration/groups.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/configuration/projects.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/configuration/transform.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/constants.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/__init__.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/commits.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/group_badges.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/group_ldap_links.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/groups.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/merge_requests.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/pipelines.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/project_badges.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/project_deploy_keys.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/project_merge_requests_approvals.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/project_protected_environments.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/projects.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/python_gitlab.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/gitlab/variables.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/lists/__init__.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/lists/filter.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/lists/groups.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/lists/projects.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/output.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/__init__.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/abstract_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/application/__init__.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/application/application_settings_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/defining_keys.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/__init__.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_badges_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_labels_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_ldap_links_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_members_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_push_rules_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_saml_links_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_settings_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_variables_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/multiple_entities_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/__init__.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/badges_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/deploy_keys_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/files_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/hooks_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/integrations_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/job_token_scope_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/members_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/merge_requests_approval_rules.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/merge_requests_approvals.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/project_labels_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/project_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/project_push_rules_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/project_security_settings.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/project_settings_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/project_variables_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/remote_mirrors_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/resource_groups_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/schedules_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/tags_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/shared/__init__.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/shared/protected_environments_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/util/__init__.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/util/decorators.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/util/difference_logger.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/util/labels_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/util/variables_processor.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/run.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/util.py +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform.egg-info/SOURCES.txt +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform.egg-info/dependency_links.txt +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform.egg-info/entry_points.txt +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform.egg-info/top_level.txt +0 -0
- {gitlabform-5.0.2 → gitlabform-5.1.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitlabform
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.1.1
|
|
4
4
|
Summary: 🏗 Specialized configuration as a code tool for GitLab projects, groups and more using hierarchical configuration written in YAML
|
|
5
5
|
Author: Greg Dubicki and Contributors
|
|
6
6
|
Project-URL: Homepage, https://gitlabform.github.io/gitlabform/
|
|
@@ -28,23 +28,23 @@ Requires-Dist: luddite==1.0.4
|
|
|
28
28
|
Requires-Dist: MarkupSafe==3.0.3
|
|
29
29
|
Requires-Dist: mergedeep==1.3.4
|
|
30
30
|
Requires-Dist: packaging==26.0
|
|
31
|
-
Requires-Dist: python-gitlab==8.
|
|
32
|
-
Requires-Dist: python-gitlab[graphql]==8.
|
|
33
|
-
Requires-Dist: requests==2.
|
|
31
|
+
Requires-Dist: python-gitlab==8.2.0
|
|
32
|
+
Requires-Dist: python-gitlab[graphql]==8.2.0
|
|
33
|
+
Requires-Dist: requests==2.33.1
|
|
34
34
|
Requires-Dist: ruamel.yaml==0.17.21
|
|
35
35
|
Requires-Dist: yamlpath==3.8.2
|
|
36
36
|
Provides-Extra: test
|
|
37
37
|
Requires-Dist: coverage==7.13.5; extra == "test"
|
|
38
|
-
Requires-Dist: cryptography==46.0.
|
|
39
|
-
Requires-Dist: deepdiff==
|
|
40
|
-
Requires-Dist: mypy==1.
|
|
38
|
+
Requires-Dist: cryptography==46.0.6; extra == "test"
|
|
39
|
+
Requires-Dist: deepdiff==9.0.0; extra == "test"
|
|
40
|
+
Requires-Dist: mypy==1.20.0; extra == "test"
|
|
41
41
|
Requires-Dist: mypy-extensions==1.1.0; extra == "test"
|
|
42
42
|
Requires-Dist: pre-commit==2.21.0; extra == "test"
|
|
43
43
|
Requires-Dist: pytest==9.0.2; extra == "test"
|
|
44
44
|
Requires-Dist: pytest-cov==7.1.0; extra == "test"
|
|
45
45
|
Requires-Dist: pytest-rerunfailures==16.1; extra == "test"
|
|
46
|
-
Requires-Dist: types-requests==2.
|
|
47
|
-
Requires-Dist: types-setuptools==82.0.0.
|
|
46
|
+
Requires-Dist: types-requests==2.33.0.20260402; extra == "test"
|
|
47
|
+
Requires-Dist: types-setuptools==82.0.0.20260402; extra == "test"
|
|
48
48
|
Requires-Dist: xkcdpass==1.30.0; extra == "test"
|
|
49
49
|
Provides-Extra: docs
|
|
50
50
|
Requires-Dist: mkdocs; extra == "docs"
|
|
@@ -179,11 +179,19 @@ class ConfigurationCore(ABC):
|
|
|
179
179
|
|
|
180
180
|
merged_dict = merge({}, more_general_config, more_specific_config)
|
|
181
181
|
|
|
182
|
-
def break_inheritance(specific_config,
|
|
182
|
+
def break_inheritance(specific_config, parent_path=()):
|
|
183
|
+
"""
|
|
184
|
+
Walk the specific config tree and replace only the exact nested section
|
|
185
|
+
that declares ``inherit: false``.
|
|
186
|
+
|
|
187
|
+
``parent_path`` stores the full key path to the current section because
|
|
188
|
+
the same section name, like ``variables``, can appear in multiple branches.
|
|
189
|
+
Replacing by section name alone can therefore update the wrong subtree.
|
|
190
|
+
"""
|
|
183
191
|
for key, value in specific_config.items():
|
|
184
192
|
if "inherit" == key:
|
|
185
193
|
if not value:
|
|
186
|
-
|
|
194
|
+
replace_config_section(merged_dict, parent_path, specific_config)
|
|
187
195
|
break
|
|
188
196
|
elif value:
|
|
189
197
|
fatal(
|
|
@@ -191,16 +199,20 @@ class ConfigurationCore(ABC):
|
|
|
191
199
|
exit_code=EXIT_INVALID_INPUT,
|
|
192
200
|
)
|
|
193
201
|
elif type(value) in [CommentedMap, dict]:
|
|
194
|
-
break_inheritance(value, key)
|
|
195
|
-
|
|
196
|
-
def
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
break_inheritance(value, parent_path + (key,))
|
|
203
|
+
|
|
204
|
+
def replace_config_section(merged_config, parent_path, specific_config):
|
|
205
|
+
"""
|
|
206
|
+
Replace the merged section at ``parent_path`` with the specific section
|
|
207
|
+
that requested an inheritance break, dropping the control flag itself.
|
|
208
|
+
"""
|
|
209
|
+
target_config = merged_config
|
|
210
|
+
for key in parent_path[:-1]:
|
|
211
|
+
target_config = target_config[key]
|
|
212
|
+
|
|
213
|
+
replacement_config = deepcopy(specific_config)
|
|
214
|
+
replacement_config.pop("inherit", None)
|
|
215
|
+
target_config[parent_path[-1]] = replacement_config
|
|
204
216
|
|
|
205
217
|
break_inheritance(more_specific_config)
|
|
206
218
|
|
|
@@ -79,6 +79,11 @@ class GitLabCore:
|
|
|
79
79
|
self.version = version_response["version"]
|
|
80
80
|
self.enterprise = version_response["enterprise"]
|
|
81
81
|
|
|
82
|
+
if self.is_version_less_than("16"):
|
|
83
|
+
warning(
|
|
84
|
+
f"Support for GitLab version {self.version} is Deprecated. See Requirements: https://gitlabform.github.io/gitlabform/requirements/"
|
|
85
|
+
)
|
|
86
|
+
|
|
82
87
|
current_user = self._make_requests_to_api("user")
|
|
83
88
|
if current_user.get("is_admin", False):
|
|
84
89
|
self.admin = True
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from logging import debug
|
|
1
|
+
from logging import debug, warning
|
|
2
2
|
from typing import Dict, Any, List
|
|
3
3
|
|
|
4
4
|
from gitlab.base import RESTObject, RESTObjectList
|
|
@@ -13,13 +13,24 @@ class GroupHooksProcessor(AbstractProcessor):
|
|
|
13
13
|
def __init__(self, gitlab: GitLab):
|
|
14
14
|
super().__init__("group_hooks", gitlab)
|
|
15
15
|
|
|
16
|
+
def _can_proceed(self, project_or_group: str, configuration: dict):
|
|
17
|
+
if not self.gitlab.enterprise:
|
|
18
|
+
hooks_in_config = configuration["group_hooks"]
|
|
19
|
+
if hooks_in_config is not None and len(hooks_in_config) > 0:
|
|
20
|
+
# Only raise error if user has defined hooks in config, otherwise exit silently out of processor
|
|
21
|
+
warning("GitLab Community Edition does not support Group Webhooks")
|
|
22
|
+
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
return True
|
|
26
|
+
|
|
16
27
|
def _process_configuration(self, group_path_and_name: str, configuration: dict):
|
|
28
|
+
hooks_in_config: tuple[str, ...] = tuple(x for x in sorted(configuration["group_hooks"]) if x != "enforce")
|
|
29
|
+
|
|
17
30
|
debug("Processing group hooks...")
|
|
18
31
|
group: Group = self.gl.get_group_by_path_cached(group_path_and_name)
|
|
19
32
|
group_hooks: list[GroupHook] = group.hooks.list(get_all=True)
|
|
20
33
|
|
|
21
|
-
hooks_in_config: tuple[str, ...] = tuple(x for x in sorted(configuration["group_hooks"]) if x != "enforce")
|
|
22
|
-
|
|
23
34
|
for hook in hooks_in_config:
|
|
24
35
|
hook_in_gitlab: RESTObject | None = next((h for h in group_hooks if h.url == hook), None)
|
|
25
36
|
hook_config = {"url": hook}
|
|
@@ -14,6 +14,16 @@ from gitlabform.processors.abstract_processor import AbstractProcessor
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class BranchesProcessor(AbstractProcessor):
|
|
17
|
+
"""
|
|
18
|
+
Processor for branch protection settings.
|
|
19
|
+
|
|
20
|
+
This processor is complex because GitLab's Protected Branches API uses different
|
|
21
|
+
data structures for Create (POST), Get (GET), and Update (PATCH) operations.
|
|
22
|
+
|
|
23
|
+
It implements 'Additive Design' (existing rules are preserved) and
|
|
24
|
+
'Raw Parameter Passing' (arbitrary keys in config are sent to the API).
|
|
25
|
+
"""
|
|
26
|
+
|
|
17
27
|
def __init__(self, gitlab: GitLab, strict: bool):
|
|
18
28
|
super().__init__("branches", gitlab)
|
|
19
29
|
self.strict = strict
|
|
@@ -37,7 +47,7 @@ class BranchesProcessor(AbstractProcessor):
|
|
|
37
47
|
def _process_configuration(self, project_and_group: str, configuration: dict):
|
|
38
48
|
"""
|
|
39
49
|
Called from process defined in abstract_processor.py after checking self._can_proceed
|
|
40
|
-
|
|
50
|
+
Iterates through all branches defined in the configuration and applies protection rules.
|
|
41
51
|
"""
|
|
42
52
|
|
|
43
53
|
project: Project = self.gl.get_project_by_path_cached(project_and_group)
|
|
@@ -49,7 +59,13 @@ class BranchesProcessor(AbstractProcessor):
|
|
|
49
59
|
|
|
50
60
|
def process_branch_protection(self, project: Project, branch_name: str, branch_config: dict):
|
|
51
61
|
"""
|
|
52
|
-
|
|
62
|
+
High-level logic for processing branch protection.
|
|
63
|
+
|
|
64
|
+
1. Validates branch existence (unless wildcard).
|
|
65
|
+
2. Handles 'protected: false' (unprotecting).
|
|
66
|
+
3. Handles 'protected: true':
|
|
67
|
+
- If not currently protected: Create protection.
|
|
68
|
+
- If protected: Update using PATCH (EE > 15.6) or Unprotect/Reprotect (CE/Old EE).
|
|
53
69
|
"""
|
|
54
70
|
protected_branch: Optional[ProjectProtectedBranch] = None
|
|
55
71
|
|
|
@@ -76,7 +92,7 @@ class BranchesProcessor(AbstractProcessor):
|
|
|
76
92
|
|
|
77
93
|
if branch_config.get("protected"):
|
|
78
94
|
if not protected_branch:
|
|
79
|
-
|
|
95
|
+
verbose(f"Creating branch protection for {branch_name}")
|
|
80
96
|
self.protect_branch(project, branch_name, branch_config, False)
|
|
81
97
|
return
|
|
82
98
|
|
|
@@ -102,20 +118,23 @@ class BranchesProcessor(AbstractProcessor):
|
|
|
102
118
|
# compare states easier to determine what PATCH request we need to send (if any)
|
|
103
119
|
transformed_branch_config = self.map_config_to_protected_branch_get_data(branch_config)
|
|
104
120
|
|
|
105
|
-
verbose("Creating data to update code_owner_approval_required as necessary")
|
|
106
|
-
# We only build PATCH data for items requiring updates, e.g. if a merge_access_level has been changed or removed,
|
|
107
|
-
# or if the code_owner_approval_required state has changed.
|
|
108
|
-
# If we send everything the PATCH endpoint will return a 200 but not apply any updates.
|
|
109
121
|
protected_branch_api_patch_data: dict = {}
|
|
110
122
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
# RAW PARAMETER PASSING (Top-Level):
|
|
124
|
+
# Iterate through any top-level flags (e.g., allow_force_push, code_owner_approval_required)
|
|
125
|
+
# and include them in the PATCH request if they differ from the current GitLab state.
|
|
126
|
+
special_list_keys = [
|
|
127
|
+
"merge_access_levels",
|
|
128
|
+
"push_access_levels",
|
|
129
|
+
"unprotect_access_levels",
|
|
130
|
+
]
|
|
131
|
+
for key, value in transformed_branch_config.items():
|
|
132
|
+
if key not in special_list_keys:
|
|
133
|
+
# Check if this attribute exists on the GitLab object and needs an update
|
|
134
|
+
existing_value = getattr(protected_branch, key, None)
|
|
135
|
+
if existing_value != value:
|
|
136
|
+
verbose(f"Creating data to update {key} as necessary")
|
|
137
|
+
protected_branch_api_patch_data[key] = value
|
|
119
138
|
|
|
120
139
|
verbose("Creating data to update merge_access_levels as necessary")
|
|
121
140
|
merge_access_items_patch_data = self.build_patch_request_data(
|
|
@@ -145,18 +164,18 @@ class BranchesProcessor(AbstractProcessor):
|
|
|
145
164
|
|
|
146
165
|
if protected_branch_api_patch_data != {}:
|
|
147
166
|
# We have some updates to make
|
|
148
|
-
|
|
167
|
+
verbose(f"Updating protected branch {branch_name} with {protected_branch_api_patch_data}")
|
|
149
168
|
self.protect_branch(project, branch_name, protected_branch_api_patch_data, True)
|
|
150
169
|
|
|
151
170
|
elif protected_branch and not branch_config.get("protected"):
|
|
152
|
-
|
|
171
|
+
verbose(f"Removing branch protection for {branch_name}")
|
|
153
172
|
self.unprotect_branch(protected_branch)
|
|
154
173
|
|
|
155
174
|
def process_branch_config_gitlab_under_15_6_0_or_ce(self, branch_config, branch_name, project, protected_branch):
|
|
156
175
|
"""
|
|
157
176
|
Processes the branches configuration for gitlab version <=15.6.0 or Community Edition,
|
|
158
|
-
|
|
159
|
-
|
|
177
|
+
where in-place updates (PATCH) are not supported or effective.
|
|
178
|
+
If a change is detected, the branch is unprotected and then reprotected from scratch.
|
|
160
179
|
"""
|
|
161
180
|
|
|
162
181
|
# Gitlab returns the allowed_to_merge etc data in a different format from GET endpoint than it takes in
|
|
@@ -224,8 +243,8 @@ class BranchesProcessor(AbstractProcessor):
|
|
|
224
243
|
|
|
225
244
|
def convert_user_and_group_names_to_ids(self, branch_config: dict):
|
|
226
245
|
"""
|
|
227
|
-
|
|
228
|
-
or group name
|
|
246
|
+
Pre-processor to resolve names to IDs.
|
|
247
|
+
Translates 'user: username' or 'group: name' into 'user_id' or 'group_id' as
|
|
229
248
|
config by replacing them with ids.
|
|
230
249
|
"""
|
|
231
250
|
verbose("Transforming User and Group names in Branch configuration to Ids")
|
|
@@ -235,10 +254,11 @@ class BranchesProcessor(AbstractProcessor):
|
|
|
235
254
|
for item in branch_config[key]:
|
|
236
255
|
if isinstance(item, dict):
|
|
237
256
|
if "user" in item:
|
|
238
|
-
|
|
257
|
+
username = item.pop("user")
|
|
258
|
+
user_id = self.gl.get_user_id_cached(username)
|
|
239
259
|
if user_id is None:
|
|
240
260
|
raise GitlabGetError(
|
|
241
|
-
f"transform_branch_config - No users found when searching for username {
|
|
261
|
+
f"transform_branch_config - No users found when searching for username {username}",
|
|
242
262
|
404,
|
|
243
263
|
)
|
|
244
264
|
item["user_id"] = user_id
|
|
@@ -250,11 +270,13 @@ class BranchesProcessor(AbstractProcessor):
|
|
|
250
270
|
@staticmethod
|
|
251
271
|
def map_config_to_protected_branch_get_data(our_branch_config: dict):
|
|
252
272
|
"""
|
|
253
|
-
|
|
254
|
-
inconsistent, the structure needed to create a branch protection rule is
|
|
255
|
-
different from structure needed to update a rule in place.
|
|
273
|
+
Normalizes the user-provided YAML config into the format returned by GitLab's GET endpoint.
|
|
256
274
|
|
|
257
|
-
|
|
275
|
+
GitLab API Mappings:
|
|
276
|
+
- 'merge_access_level' (Standard) -> 'merge_access_levels' (List)
|
|
277
|
+
- 'allowed_to_merge' (Premium) -> 'merge_access_levels' (List)
|
|
278
|
+
|
|
279
|
+
This transformation allows for a direct comparison between the desired state and current state.
|
|
258
280
|
This method will normalize gitlabform branch_config to accommodate this.
|
|
259
281
|
|
|
260
282
|
Args:
|
|
@@ -277,29 +299,40 @@ class BranchesProcessor(AbstractProcessor):
|
|
|
277
299
|
new_branch_config = our_branch_config.copy()
|
|
278
300
|
for key in our_branch_config:
|
|
279
301
|
if key in local_keys_to_gitlab_keys_map.keys():
|
|
302
|
+
target_key = local_keys_to_gitlab_keys_map[key]
|
|
280
303
|
# *_access_level in gitlabform will have been transformed to it's int representation already if defined
|
|
281
304
|
# by the user as "merge_access_level: Maintainer"
|
|
282
305
|
if isinstance(our_branch_config[key], int):
|
|
283
306
|
access_level = new_branch_config.pop(key)
|
|
284
|
-
new_branch_config[
|
|
285
|
-
{
|
|
307
|
+
new_branch_config[target_key] = [
|
|
308
|
+
{
|
|
309
|
+
"id": None,
|
|
310
|
+
"access_level": access_level,
|
|
311
|
+
"user_id": None,
|
|
312
|
+
"group_id": None,
|
|
313
|
+
"deploy_key_id": None,
|
|
314
|
+
}
|
|
286
315
|
]
|
|
287
316
|
# allowed_to_* are lists...
|
|
288
317
|
elif isinstance(our_branch_config[key], list):
|
|
289
|
-
|
|
318
|
+
mapped_list = []
|
|
290
319
|
for item in our_branch_config[key]:
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
320
|
+
# RAW PARAMETER PASSING: We ensure the core identity keys exist for comparison,
|
|
321
|
+
# but we preserve all other arbitrary keys provided by the user.
|
|
322
|
+
mapped_item = {
|
|
323
|
+
"id": None,
|
|
324
|
+
"access_level": item.get("access_level"),
|
|
325
|
+
"user_id": item.get("user_id"),
|
|
326
|
+
"group_id": item.get("group_id"),
|
|
327
|
+
"deploy_key_id": item.get("deploy_key_id"),
|
|
328
|
+
**{
|
|
329
|
+
k: v
|
|
330
|
+
for k, v in item.items()
|
|
331
|
+
if k not in ["access_level", "user_id", "group_id", "deploy_key_id", "id"]
|
|
332
|
+
},
|
|
333
|
+
}
|
|
334
|
+
mapped_list.append(mapped_item)
|
|
335
|
+
new_branch_config[target_key] = mapped_list
|
|
303
336
|
new_branch_config.pop(key)
|
|
304
337
|
|
|
305
338
|
# this key is not present in
|
|
@@ -311,13 +344,13 @@ class BranchesProcessor(AbstractProcessor):
|
|
|
311
344
|
@staticmethod
|
|
312
345
|
def build_patch_request_data(transformed_access_levels: list[dict] | None, existing_records: tuple) -> list[dict]:
|
|
313
346
|
"""
|
|
314
|
-
|
|
315
|
-
If there are no changes for a given access_level record an empty list is returned.
|
|
316
|
-
Otherwise, data is returned to add/update access_level records and remove any outdated records.
|
|
347
|
+
Calculates the specific payload for the PATCH (update) API.
|
|
317
348
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
349
|
+
Matching Logic:
|
|
350
|
+
1. For each rule in the config, check if an identical rule already exists in GitLab.
|
|
351
|
+
2. Rule Identity is determined by: user_id OR group_id OR deploy_key_id OR (role-only access_level).
|
|
352
|
+
3. If a match is found, the rule is skipped (idempotency).
|
|
353
|
+
4. If 'No Access' (0) is requested but other roles exist, the existing roles are marked for destruction.
|
|
321
354
|
|
|
322
355
|
args:
|
|
323
356
|
transformed_access_levels (list[dict|None]): transformed merge_access_levels or push_access_levels or unprotect_access_levels configration generated by transform_branch_config_access_levels
|
|
@@ -330,43 +363,11 @@ class BranchesProcessor(AbstractProcessor):
|
|
|
330
363
|
|
|
331
364
|
if transformed_access_levels is not None:
|
|
332
365
|
# User has defined in configuration some access level for this resource on the protected branch
|
|
333
|
-
#
|
|
334
|
-
#
|
|
366
|
+
# Prepare patch data using raw parameters from config.
|
|
367
|
+
# We only send non-None values and omit the internal 'id' placeholder used for mapping.
|
|
335
368
|
for configuration in transformed_access_levels:
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
configured_group_id = configuration.get("group_id")
|
|
339
|
-
|
|
340
|
-
if configured_access_level is not None:
|
|
341
|
-
# Entry to configure an access level e.g.
|
|
342
|
-
# push_access_level: Maintainer
|
|
343
|
-
#
|
|
344
|
-
# or:
|
|
345
|
-
# allowed_to_push:
|
|
346
|
-
# - access_level: Maintainer
|
|
347
|
-
patch_data.append(
|
|
348
|
-
{
|
|
349
|
-
"access_level": configured_access_level,
|
|
350
|
-
}
|
|
351
|
-
)
|
|
352
|
-
elif configured_user_id is not None:
|
|
353
|
-
# Entry to configure a user to have access, only available for users with "Premium" or "Ultimate" e.g.
|
|
354
|
-
# allowed_to_push:
|
|
355
|
-
# - user: tim-knight
|
|
356
|
-
patch_data.append(
|
|
357
|
-
{
|
|
358
|
-
"user_id": configured_user_id,
|
|
359
|
-
}
|
|
360
|
-
)
|
|
361
|
-
else:
|
|
362
|
-
# Entry to configure a group to have access, only available for users with "Premium" or "Ultimate" e.g.
|
|
363
|
-
# allowed_to_push:
|
|
364
|
-
# - group_id: 15
|
|
365
|
-
patch_data.append(
|
|
366
|
-
{
|
|
367
|
-
"group_id": configured_group_id,
|
|
368
|
-
}
|
|
369
|
-
)
|
|
369
|
+
item_to_add = {k: v for k, v in configuration.items() if v is not None and k != "id"}
|
|
370
|
+
patch_data.append(item_to_add)
|
|
370
371
|
|
|
371
372
|
# Check if we need to make the update (e.g. an existing record for that user_id or access_level exists)
|
|
372
373
|
# and mark existing records for deletion if the access_level does not match
|
|
@@ -376,8 +377,10 @@ class BranchesProcessor(AbstractProcessor):
|
|
|
376
377
|
existing_records_access_level = existing_record.get("access_level")
|
|
377
378
|
existing_records_user_id = existing_record.get("user_id")
|
|
378
379
|
existing_records_group_id = existing_record.get("group_id")
|
|
380
|
+
existing_records_deploy_key_id = existing_record.get("deploy_key_id")
|
|
379
381
|
|
|
380
|
-
|
|
382
|
+
is_match = False
|
|
383
|
+
matching_item_in_patch = None
|
|
381
384
|
|
|
382
385
|
for item in patch_data:
|
|
383
386
|
# We prioritize user_id and group_id matches over access_level.
|
|
@@ -385,24 +388,41 @@ class BranchesProcessor(AbstractProcessor):
|
|
|
385
388
|
# If we matched by access_level first, we might incorrectly pair a specific user's rule
|
|
386
389
|
# with a generic role-based rule from the config.
|
|
387
390
|
if existing_records_user_id is not None and item.get("user_id") == existing_records_user_id:
|
|
388
|
-
|
|
389
|
-
break
|
|
391
|
+
is_match = True
|
|
390
392
|
elif existing_records_group_id is not None and item.get("group_id") == existing_records_group_id:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
+
is_match = True
|
|
394
|
+
elif (
|
|
395
|
+
existing_records_deploy_key_id is not None
|
|
396
|
+
and item.get("deploy_key_id") == existing_records_deploy_key_id
|
|
397
|
+
):
|
|
398
|
+
is_match = True
|
|
393
399
|
elif (
|
|
394
|
-
|
|
395
|
-
and item.get("
|
|
400
|
+
item.get("user_id") is None
|
|
401
|
+
and item.get("group_id") is None
|
|
402
|
+
and item.get("deploy_key_id") is None
|
|
396
403
|
):
|
|
397
|
-
|
|
404
|
+
# Role-based rule logic
|
|
405
|
+
local_level = item.get("access_level")
|
|
406
|
+
if local_level == existing_records_access_level:
|
|
407
|
+
is_match = True
|
|
408
|
+
elif local_level == 0 or existing_records_access_level == 0:
|
|
409
|
+
# "No Access" (0) is mutually exclusive with any other role.
|
|
410
|
+
# Mark existing for destruction so the new state can be applied.
|
|
411
|
+
record_id = existing_record.get("id")
|
|
412
|
+
patch_data.append({"id": record_id, "_destroy": True})
|
|
413
|
+
break
|
|
414
|
+
|
|
415
|
+
if is_match:
|
|
416
|
+
matching_item_in_patch = item
|
|
417
|
+
break
|
|
418
|
+
|
|
419
|
+
if matching_item_in_patch:
|
|
420
|
+
# Rule already exists in GitLab, remove from the patch list to maintain idempotency
|
|
421
|
+
patch_data.remove(matching_item_in_patch)
|
|
422
|
+
|
|
423
|
+
# NOTE: GitLabForm is additive. We do not destroy existing records unless they
|
|
424
|
+
# conflict with a 'No Access' rule.
|
|
398
425
|
|
|
399
|
-
if matching_item_to_be_created is not None:
|
|
400
|
-
# If we found an existing item matching one that has been configured, remove the item
|
|
401
|
-
# to be created and don't mark the existing record for deletion
|
|
402
|
-
patch_data.remove(matching_item_to_be_created)
|
|
403
|
-
else:
|
|
404
|
-
record_id = existing_record.get("id")
|
|
405
|
-
patch_data.append({"id": record_id, "_destroy": True})
|
|
406
426
|
else:
|
|
407
427
|
verbose("No configuration defined for this access level. No changes will be made.")
|
|
408
428
|
|
|
@@ -410,36 +430,68 @@ class BranchesProcessor(AbstractProcessor):
|
|
|
410
430
|
|
|
411
431
|
@staticmethod
|
|
412
432
|
def naive_access_level_diff_analyzer(_, cfg_in_gitlab: list, local_cfg: list):
|
|
433
|
+
"""
|
|
434
|
+
Custom diff analyzer for branch rules.
|
|
435
|
+
|
|
436
|
+
Following GitLabForm's ADDITIVE DESIGN, an update is needed if any rule
|
|
437
|
+
defined in the local configuration is missing from GitLab.
|
|
438
|
+
"""
|
|
439
|
+
# 1. Check if any local rule is missing from GitLab (Additive check)
|
|
440
|
+
for local_item in local_cfg:
|
|
441
|
+
found = False
|
|
442
|
+
for gl_item in cfg_in_gitlab:
|
|
443
|
+
# User Match
|
|
444
|
+
if local_item.get("user_id") is not None:
|
|
445
|
+
if local_item.get("user_id") == gl_item.get("user_id"):
|
|
446
|
+
found = True
|
|
447
|
+
break
|
|
448
|
+
# Group Match
|
|
449
|
+
elif local_item.get("group_id") is not None:
|
|
450
|
+
if local_item.get("group_id") == gl_item.get("group_id"):
|
|
451
|
+
found = True
|
|
452
|
+
break
|
|
453
|
+
# Deploy Key Match
|
|
454
|
+
elif local_item.get("deploy_key_id") is not None:
|
|
455
|
+
if local_item.get("deploy_key_id") == gl_item.get("deploy_key_id"):
|
|
456
|
+
found = True
|
|
457
|
+
break
|
|
458
|
+
# Role Match (Ensure GL item is also a role rule)
|
|
459
|
+
if (
|
|
460
|
+
local_item.get("access_level") is not None
|
|
461
|
+
and local_item.get("access_level") == gl_item.get("access_level")
|
|
462
|
+
and gl_item.get("user_id") is None
|
|
463
|
+
and gl_item.get("group_id") is None
|
|
464
|
+
and gl_item.get("deploy_key_id") is None
|
|
465
|
+
):
|
|
466
|
+
found = True
|
|
467
|
+
break
|
|
468
|
+
|
|
469
|
+
if not found:
|
|
470
|
+
verbose("naive_access_level_diff_analyzer - needs_update: True (missing rule found)")
|
|
471
|
+
return True
|
|
472
|
+
|
|
473
|
+
# 2. Role exclusivity check (No Access handling)
|
|
474
|
+
# Even if all local rules are "found", we need an update if GitLab has a "No Access" rule
|
|
475
|
+
# while we want specific roles, or vice-versa.
|
|
476
|
+
gl_role_levels = {
|
|
477
|
+
r.get("access_level")
|
|
478
|
+
for r in cfg_in_gitlab
|
|
479
|
+
if r.get("user_id") is None and r.get("group_id") is None and r.get("deploy_key_id") is None
|
|
480
|
+
}
|
|
481
|
+
local_role_levels = {
|
|
482
|
+
r.get("access_level")
|
|
483
|
+
for r in local_cfg
|
|
484
|
+
if r.get("user_id") is None and r.get("group_id") is None and r.get("deploy_key_id") is None
|
|
485
|
+
}
|
|
413
486
|
|
|
414
|
-
if
|
|
487
|
+
if (0 in gl_role_levels and any(lev > 0 for lev in local_role_levels)) or (
|
|
488
|
+
0 in local_role_levels and any(lev > 0 for lev in gl_role_levels)
|
|
489
|
+
):
|
|
490
|
+
verbose("naive_access_level_diff_analyzer - needs_update: True (No Access / Roles conflict)")
|
|
415
491
|
return True
|
|
416
492
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
# so we take a naive approach here and kind of expect it will always be
|
|
420
|
-
# either one of those, but not a combination
|
|
421
|
-
needs_update = False
|
|
422
|
-
changes_found = 0
|
|
423
|
-
for item in local_cfg:
|
|
424
|
-
if item["access_level"] and item["access_level"] >= 0:
|
|
425
|
-
access_level = item["access_level"]
|
|
426
|
-
for gl_item in cfg_in_gitlab:
|
|
427
|
-
if gl_item["access_level"] != access_level:
|
|
428
|
-
changes_found += 1
|
|
429
|
-
elif item["user_id"] and item["user_id"] >= 0:
|
|
430
|
-
user_id = item["user_id"]
|
|
431
|
-
for gl_item in cfg_in_gitlab:
|
|
432
|
-
if gl_item["user_id"] != user_id:
|
|
433
|
-
changes_found += 1
|
|
434
|
-
elif item["group_id"] and item["group_id"] >= 0:
|
|
435
|
-
group_id = item["group_id"]
|
|
436
|
-
for gl_item in cfg_in_gitlab:
|
|
437
|
-
if gl_item["group_id"] != group_id:
|
|
438
|
-
changes_found += 1
|
|
439
|
-
if changes_found > 0:
|
|
440
|
-
needs_update = True
|
|
441
|
-
verbose(f"naive_access_level_diff_analyzer - needs_update: {needs_update}, changes_found: {changes_found}")
|
|
442
|
-
return needs_update
|
|
493
|
+
verbose("naive_access_level_diff_analyzer - needs_update: False")
|
|
494
|
+
return False
|
|
443
495
|
|
|
444
496
|
@staticmethod
|
|
445
497
|
def branch_name_contains_supported_wildcard(branch):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitlabform
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.1.1
|
|
4
4
|
Summary: 🏗 Specialized configuration as a code tool for GitLab projects, groups and more using hierarchical configuration written in YAML
|
|
5
5
|
Author: Greg Dubicki and Contributors
|
|
6
6
|
Project-URL: Homepage, https://gitlabform.github.io/gitlabform/
|
|
@@ -28,23 +28,23 @@ Requires-Dist: luddite==1.0.4
|
|
|
28
28
|
Requires-Dist: MarkupSafe==3.0.3
|
|
29
29
|
Requires-Dist: mergedeep==1.3.4
|
|
30
30
|
Requires-Dist: packaging==26.0
|
|
31
|
-
Requires-Dist: python-gitlab==8.
|
|
32
|
-
Requires-Dist: python-gitlab[graphql]==8.
|
|
33
|
-
Requires-Dist: requests==2.
|
|
31
|
+
Requires-Dist: python-gitlab==8.2.0
|
|
32
|
+
Requires-Dist: python-gitlab[graphql]==8.2.0
|
|
33
|
+
Requires-Dist: requests==2.33.1
|
|
34
34
|
Requires-Dist: ruamel.yaml==0.17.21
|
|
35
35
|
Requires-Dist: yamlpath==3.8.2
|
|
36
36
|
Provides-Extra: test
|
|
37
37
|
Requires-Dist: coverage==7.13.5; extra == "test"
|
|
38
|
-
Requires-Dist: cryptography==46.0.
|
|
39
|
-
Requires-Dist: deepdiff==
|
|
40
|
-
Requires-Dist: mypy==1.
|
|
38
|
+
Requires-Dist: cryptography==46.0.6; extra == "test"
|
|
39
|
+
Requires-Dist: deepdiff==9.0.0; extra == "test"
|
|
40
|
+
Requires-Dist: mypy==1.20.0; extra == "test"
|
|
41
41
|
Requires-Dist: mypy-extensions==1.1.0; extra == "test"
|
|
42
42
|
Requires-Dist: pre-commit==2.21.0; extra == "test"
|
|
43
43
|
Requires-Dist: pytest==9.0.2; extra == "test"
|
|
44
44
|
Requires-Dist: pytest-cov==7.1.0; extra == "test"
|
|
45
45
|
Requires-Dist: pytest-rerunfailures==16.1; extra == "test"
|
|
46
|
-
Requires-Dist: types-requests==2.
|
|
47
|
-
Requires-Dist: types-setuptools==82.0.0.
|
|
46
|
+
Requires-Dist: types-requests==2.33.0.20260402; extra == "test"
|
|
47
|
+
Requires-Dist: types-setuptools==82.0.0.20260402; extra == "test"
|
|
48
48
|
Requires-Dist: xkcdpass==1.30.0; extra == "test"
|
|
49
49
|
Provides-Extra: docs
|
|
50
50
|
Requires-Dist: mkdocs; extra == "docs"
|
|
@@ -6,9 +6,9 @@ luddite==1.0.4
|
|
|
6
6
|
MarkupSafe==3.0.3
|
|
7
7
|
mergedeep==1.3.4
|
|
8
8
|
packaging==26.0
|
|
9
|
-
python-gitlab==8.
|
|
10
|
-
python-gitlab[graphql]==8.
|
|
11
|
-
requests==2.
|
|
9
|
+
python-gitlab==8.2.0
|
|
10
|
+
python-gitlab[graphql]==8.2.0
|
|
11
|
+
requests==2.33.1
|
|
12
12
|
ruamel.yaml==0.17.21
|
|
13
13
|
yamlpath==3.8.2
|
|
14
14
|
|
|
@@ -18,14 +18,14 @@ mkdocs-material
|
|
|
18
18
|
|
|
19
19
|
[test]
|
|
20
20
|
coverage==7.13.5
|
|
21
|
-
cryptography==46.0.
|
|
22
|
-
deepdiff==
|
|
23
|
-
mypy==1.
|
|
21
|
+
cryptography==46.0.6
|
|
22
|
+
deepdiff==9.0.0
|
|
23
|
+
mypy==1.20.0
|
|
24
24
|
mypy-extensions==1.1.0
|
|
25
25
|
pre-commit==2.21.0
|
|
26
26
|
pytest==9.0.2
|
|
27
27
|
pytest-cov==7.1.0
|
|
28
28
|
pytest-rerunfailures==16.1
|
|
29
|
-
types-requests==2.
|
|
30
|
-
types-setuptools==82.0.0.
|
|
29
|
+
types-requests==2.33.0.20260402
|
|
30
|
+
types-setuptools==82.0.0.20260402
|
|
31
31
|
xkcdpass==1.30.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gitlabform"
|
|
7
|
-
version = "5.
|
|
7
|
+
version = "5.1.1"
|
|
8
8
|
authors = [{ name = "Greg Dubicki and Contributors" }]
|
|
9
9
|
description = "🏗 Specialized configuration as a code tool for GitLab projects, groups and more using hierarchical configuration written in YAML"
|
|
10
10
|
keywords = ["cli", "yaml", "gitlab", "configuration-as-code"]
|
|
@@ -31,9 +31,9 @@ dependencies = [
|
|
|
31
31
|
"MarkupSafe==3.0.3",
|
|
32
32
|
"mergedeep==1.3.4",
|
|
33
33
|
"packaging==26.0",
|
|
34
|
-
"python-gitlab==8.
|
|
35
|
-
"python-gitlab[graphql]==8.
|
|
36
|
-
"requests==2.
|
|
34
|
+
"python-gitlab==8.2.0",
|
|
35
|
+
"python-gitlab[graphql]==8.2.0",
|
|
36
|
+
"requests==2.33.1",
|
|
37
37
|
"ruamel.yaml==0.17.21",
|
|
38
38
|
"yamlpath==3.8.2",
|
|
39
39
|
]
|
|
@@ -51,16 +51,16 @@ content-type = "text/markdown"
|
|
|
51
51
|
[project.optional-dependencies]
|
|
52
52
|
test = [
|
|
53
53
|
"coverage==7.13.5",
|
|
54
|
-
"cryptography==46.0.
|
|
55
|
-
"deepdiff==
|
|
56
|
-
"mypy==1.
|
|
54
|
+
"cryptography==46.0.6",
|
|
55
|
+
"deepdiff==9.0.0",
|
|
56
|
+
"mypy==1.20.0",
|
|
57
57
|
"mypy-extensions==1.1.0",
|
|
58
58
|
"pre-commit==2.21.0",
|
|
59
59
|
"pytest==9.0.2",
|
|
60
60
|
"pytest-cov==7.1.0",
|
|
61
61
|
"pytest-rerunfailures==16.1",
|
|
62
|
-
"types-requests==2.
|
|
63
|
-
"types-setuptools==82.0.0.
|
|
62
|
+
"types-requests==2.33.0.20260402",
|
|
63
|
+
"types-setuptools==82.0.0.20260402",
|
|
64
64
|
"xkcdpass==1.30.0",
|
|
65
65
|
]
|
|
66
66
|
docs = ["mkdocs", "mkdocs-material"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_ldap_links_processor.py
RENAMED
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_members_processor.py
RENAMED
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_push_rules_processor.py
RENAMED
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_saml_links_processor.py
RENAMED
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_settings_processor.py
RENAMED
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/group/group_variables_processor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/deploy_keys_processor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/integrations_processor.py
RENAMED
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/job_token_scope_processor.py
RENAMED
|
File without changes
|
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/merge_requests_approval_rules.py
RENAMED
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/merge_requests_approvals.py
RENAMED
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/project_labels_processor.py
RENAMED
|
File without changes
|
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/project_push_rules_processor.py
RENAMED
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/project_security_settings.py
RENAMED
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/project_settings_processor.py
RENAMED
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/project_variables_processor.py
RENAMED
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/remote_mirrors_processor.py
RENAMED
|
File without changes
|
{gitlabform-5.0.2 → gitlabform-5.1.1}/gitlabform/processors/project/resource_groups_processor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|