gitlabform 4.2.5__tar.gz → 4.3.0__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-4.2.5 → gitlabform-4.3.0}/PKG-INFO +15 -15
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/python_gitlab.py +1 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/group/group_hooks_processor.py +2 -1
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/group/group_members_processor.py +36 -32
- gitlabform-4.3.0/gitlabform/processors/group/group_settings_processor.py +90 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/hooks_processor.py +2 -1
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/integrations_processor.py +10 -1
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/members_processor.py +15 -30
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/project_settings_processor.py +62 -1
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/schedules_processor.py +1 -1
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/util/labels_processor.py +2 -2
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform.egg-info/PKG-INFO +15 -15
- gitlabform-4.3.0/gitlabform.egg-info/requires.txt +31 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/pyproject.toml +15 -15
- gitlabform-4.2.5/gitlabform/processors/group/group_settings_processor.py +0 -30
- gitlabform-4.2.5/gitlabform.egg-info/requires.txt +0 -31
- {gitlabform-4.2.5 → gitlabform-4.3.0}/LICENSE +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/README.md +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/__init__.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/configuration/__init__.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/configuration/common.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/configuration/core.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/configuration/groups.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/configuration/projects.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/configuration/transform.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/constants.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/__init__.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/commits.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/core.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/group_badges.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/group_ldap_links.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/group_variables.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/groups.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/merge_requests.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/pipelines.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/project_badges.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/project_deploy_keys.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/project_merge_requests_approvals.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/project_protected_environments.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/project_security_settings.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/projects.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/gitlab/variables.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/lists/__init__.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/lists/filter.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/lists/groups.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/lists/projects.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/output.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/__init__.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/abstract_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/application/__init__.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/application/application_settings_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/defining_keys.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/group/__init__.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/group/group_badges_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/group/group_labels_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/group/group_ldap_links_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/group/group_push_rules_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/group/group_saml_links_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/group/group_variables_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/multiple_entities_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/__init__.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/badges_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/branches_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/deploy_keys_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/files_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/job_token_scope_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/merge_requests_approval_rules.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/merge_requests_approvals.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/project_labels_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/project_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/project_push_rules_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/project_security_settings.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/resource_groups_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/tags_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/variables_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/shared/__init__.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/shared/protected_environments_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/single_entity_processor.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/util/__init__.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/util/decorators.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/util/difference_logger.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/run.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/util.py +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform.egg-info/SOURCES.txt +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform.egg-info/dependency_links.txt +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform.egg-info/entry_points.txt +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform.egg-info/top_level.txt +0 -0
- {gitlabform-4.2.5 → gitlabform-4.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitlabform
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.0
|
|
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/
|
|
@@ -20,7 +20,7 @@ Classifier: Topic :: Software Development :: Version Control :: Git
|
|
|
20
20
|
Requires-Python: >=3.12.0
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: certifi==2025.
|
|
23
|
+
Requires-Dist: certifi==2025.7.14
|
|
24
24
|
Requires-Dist: cli-ui==0.19.0
|
|
25
25
|
Requires-Dist: ez-yaml==1.2.0
|
|
26
26
|
Requires-Dist: Jinja2==3.1.6
|
|
@@ -28,23 +28,23 @@ Requires-Dist: luddite==1.0.4
|
|
|
28
28
|
Requires-Dist: MarkupSafe==3.0.2
|
|
29
29
|
Requires-Dist: mergedeep==1.3.4
|
|
30
30
|
Requires-Dist: packaging==25.0
|
|
31
|
-
Requires-Dist: python-gitlab==
|
|
32
|
-
Requires-Dist: python-gitlab[graphql]==
|
|
33
|
-
Requires-Dist: requests==2.32.
|
|
31
|
+
Requires-Dist: python-gitlab==6.1.0
|
|
32
|
+
Requires-Dist: python-gitlab[graphql]==6.1.0
|
|
33
|
+
Requires-Dist: requests==2.32.4
|
|
34
34
|
Requires-Dist: ruamel.yaml==0.17.21
|
|
35
|
-
Requires-Dist: types-requests==2.32.
|
|
36
|
-
Requires-Dist: types-setuptools==
|
|
35
|
+
Requires-Dist: types-requests==2.32.4.20250611
|
|
36
|
+
Requires-Dist: types-setuptools==80.9.0.20250529
|
|
37
37
|
Requires-Dist: yamlpath==3.8.2
|
|
38
38
|
Provides-Extra: test
|
|
39
|
-
Requires-Dist: coverage==7.
|
|
40
|
-
Requires-Dist: cryptography==
|
|
41
|
-
Requires-Dist: deepdiff==8.
|
|
42
|
-
Requires-Dist: mypy==1.
|
|
43
|
-
Requires-Dist: mypy-extensions==1.
|
|
39
|
+
Requires-Dist: coverage==7.9.2; extra == "test"
|
|
40
|
+
Requires-Dist: cryptography==45.0.5; extra == "test"
|
|
41
|
+
Requires-Dist: deepdiff==8.5.0; extra == "test"
|
|
42
|
+
Requires-Dist: mypy==1.17.0; extra == "test"
|
|
43
|
+
Requires-Dist: mypy-extensions==1.1.0; extra == "test"
|
|
44
44
|
Requires-Dist: pre-commit==2.21.0; extra == "test"
|
|
45
|
-
Requires-Dist: pytest==8.
|
|
46
|
-
Requires-Dist: pytest-cov==6.
|
|
47
|
-
Requires-Dist: pytest-rerunfailures==15.
|
|
45
|
+
Requires-Dist: pytest==8.4.1; extra == "test"
|
|
46
|
+
Requires-Dist: pytest-cov==6.2.1; extra == "test"
|
|
47
|
+
Requires-Dist: pytest-rerunfailures==15.1; extra == "test"
|
|
48
48
|
Requires-Dist: xkcdpass==1.20.0; extra == "test"
|
|
49
49
|
Provides-Extra: docs
|
|
50
50
|
Requires-Dist: mkdocs; extra == "docs"
|
|
@@ -96,6 +96,7 @@ class PythonGitlab(Gitlab):
|
|
|
96
96
|
# Gitlab API will only ever return 0 or 1 entry when GETting using `username` attribute
|
|
97
97
|
# https://docs.gitlab.com/ee/api/users.html#for-non-administrator-users
|
|
98
98
|
# so will always be list[RESTObject] and never RESTObjectList from python-gitlab's api
|
|
99
|
+
# username list is case-insensitive on the gitlab api
|
|
99
100
|
users: list[RESTObject] = self.users.list(username=username) # type: ignore
|
|
100
101
|
|
|
101
102
|
if len(users) == 0:
|
|
@@ -3,6 +3,7 @@ from typing import Dict, Any, List
|
|
|
3
3
|
|
|
4
4
|
from gitlab.base import RESTObject, RESTObjectList
|
|
5
5
|
from gitlab.v4.objects import Group
|
|
6
|
+
from gitlab.v4.objects import GroupHook
|
|
6
7
|
|
|
7
8
|
from gitlabform.gitlab import GitLab
|
|
8
9
|
from gitlabform.processors.abstract_processor import AbstractProcessor
|
|
@@ -15,7 +16,7 @@ class GroupHooksProcessor(AbstractProcessor):
|
|
|
15
16
|
def _process_configuration(self, group_path_and_name: str, configuration: dict):
|
|
16
17
|
debug("Processing group hooks...")
|
|
17
18
|
group: Group = self.gl.get_group_by_path_cached(group_path_and_name)
|
|
18
|
-
group_hooks:
|
|
19
|
+
group_hooks: list[GroupHook] = group.hooks.list(get_all=True)
|
|
19
20
|
|
|
20
21
|
hooks_in_config: tuple[str, ...] = tuple(x for x in sorted(configuration["group_hooks"]) if x != "enforce")
|
|
21
22
|
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/group/group_members_processor.py
RENAMED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
from logging import debug, warning
|
|
2
1
|
from typing import Dict, Tuple
|
|
3
2
|
|
|
4
|
-
import
|
|
5
|
-
from cli_ui import fatal, error, debug as verbose
|
|
3
|
+
from cli_ui import info, fatal, error, debug as verbose
|
|
6
4
|
|
|
7
5
|
from gitlabform.constants import EXIT_INVALID_INPUT
|
|
8
6
|
from gitlabform.gitlab import GitLab, AccessLevel
|
|
9
7
|
from gitlabform.processors.abstract_processor import AbstractProcessor
|
|
10
8
|
from gitlab.v4.objects import Group, GroupMember, User
|
|
11
|
-
from gitlab import GitlabDeleteError, GitlabGetError
|
|
9
|
+
from gitlab import GitlabDeleteError, GitlabError, GitlabGetError
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
class GroupMembersProcessor(AbstractProcessor):
|
|
@@ -68,7 +66,7 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
68
66
|
enforce_group_members: bool,
|
|
69
67
|
):
|
|
70
68
|
shared_with_groups_before = group_being_processed.shared_with_groups
|
|
71
|
-
|
|
69
|
+
verbose("Group shared with BEFORE: %s", shared_with_groups_before)
|
|
72
70
|
|
|
73
71
|
groups_before_by_group_path = dict()
|
|
74
72
|
for shared_with_group in shared_with_groups_before:
|
|
@@ -88,45 +86,49 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
88
86
|
expires_at_before = groups_before_by_group_path[share_with_group_path]["expires_at"]
|
|
89
87
|
|
|
90
88
|
if group_access_before == group_access_to_set and expires_at_before == expires_at_to_set:
|
|
91
|
-
|
|
89
|
+
verbose(
|
|
92
90
|
"Nothing to change for group '%s' - same config now as to set.",
|
|
93
91
|
share_with_group_path,
|
|
94
92
|
)
|
|
95
93
|
else:
|
|
96
|
-
|
|
97
|
-
"Re-adding group '%s' to change their access level or expires at.",
|
|
98
|
-
share_with_group_path,
|
|
99
|
-
)
|
|
94
|
+
info(f"Re-adding group {share_with_group_path} to change their access level or expires at.")
|
|
100
95
|
share_with_group_id = groups_before_by_group_path[share_with_group_path]["group_id"]
|
|
101
96
|
# we will remove the group first and then re-add them,
|
|
102
97
|
# to ensure that the group has the expected access level
|
|
103
98
|
self._unshare(group_being_processed, share_with_group_id)
|
|
104
99
|
|
|
105
|
-
|
|
100
|
+
try:
|
|
101
|
+
group_being_processed.share(share_with_group_id, group_access_to_set, expires_at_to_set)
|
|
102
|
+
except GitlabError as e:
|
|
103
|
+
error(f"Error processing {share_with_group_path}, {e.error_message}")
|
|
104
|
+
raise e
|
|
106
105
|
|
|
107
106
|
else:
|
|
108
|
-
|
|
109
|
-
"Adding group
|
|
110
|
-
share_with_group_path,
|
|
107
|
+
verbose(
|
|
108
|
+
f"Adding group {share_with_group_path} who previously was not a member.",
|
|
111
109
|
)
|
|
112
110
|
|
|
113
111
|
share_with_group_id = self.gl.get_group_id(share_with_group_path)
|
|
114
|
-
|
|
112
|
+
try:
|
|
113
|
+
group_being_processed.share(share_with_group_id, group_access_to_set, expires_at_to_set)
|
|
114
|
+
except GitlabError as e:
|
|
115
|
+
error(f"Error processing {share_with_group_path}, {e.error_message}")
|
|
116
|
+
raise e
|
|
115
117
|
|
|
116
118
|
if enforce_group_members:
|
|
117
119
|
# remove groups not configured explicitly
|
|
118
120
|
groups_not_configured = set(groups_before_by_group_path) - set(groups_to_share_with_by_path)
|
|
119
121
|
for group_path in groups_not_configured:
|
|
120
|
-
|
|
122
|
+
verbose(
|
|
121
123
|
"Removing group '%s' who is not configured to be a member.",
|
|
122
124
|
group_path,
|
|
123
125
|
)
|
|
124
126
|
share_with_group_id = self.gl.get_group_id(group_path)
|
|
125
127
|
self._unshare(group_being_processed, share_with_group_id)
|
|
126
128
|
else:
|
|
127
|
-
|
|
129
|
+
verbose("Not enforcing group members.")
|
|
128
130
|
|
|
129
|
-
|
|
131
|
+
verbose(
|
|
130
132
|
"Group shared with AFTER: %s",
|
|
131
133
|
group_being_processed.members.list(get_all=True),
|
|
132
134
|
)
|
|
@@ -136,7 +138,7 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
136
138
|
try:
|
|
137
139
|
group_being_processed.unshare(share_with_group_id)
|
|
138
140
|
except GitlabDeleteError:
|
|
139
|
-
|
|
141
|
+
info(f"Group with id {share_with_group_id} could not be unshared, likely was never shared to begin with")
|
|
140
142
|
pass
|
|
141
143
|
|
|
142
144
|
def _process_users(
|
|
@@ -150,7 +152,7 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
150
152
|
# (note: we DON'T get inherited users as we don't manage them at this level anyway)
|
|
151
153
|
users_before = self.get_group_members(group)
|
|
152
154
|
|
|
153
|
-
|
|
155
|
+
verbose("Group members BEFORE: %s", users_before.keys())
|
|
154
156
|
|
|
155
157
|
if users_to_set_by_username:
|
|
156
158
|
# group users to set by access level
|
|
@@ -210,26 +212,28 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
210
212
|
and expires_at_before == expires_at_to_set
|
|
211
213
|
and member_role_id_before == member_role_id_to_set
|
|
212
214
|
):
|
|
213
|
-
|
|
215
|
+
verbose(
|
|
214
216
|
"Nothing to change for user '%s' - same config now as to set.",
|
|
215
217
|
common_username,
|
|
216
218
|
)
|
|
217
219
|
else:
|
|
218
|
-
|
|
219
|
-
"Editing user
|
|
220
|
-
|
|
220
|
+
verbose(
|
|
221
|
+
f"Editing user {common_username} to change their access level to {access_level_to_set},"
|
|
222
|
+
f" expires at to {expires_at_to_set},"
|
|
223
|
+
f" and member_role_id to {member_role_id_to_set}."
|
|
221
224
|
)
|
|
222
225
|
|
|
223
226
|
group_member.access_level = access_level_to_set
|
|
224
227
|
group_member.expires_at = expires_at_to_set
|
|
225
228
|
group_member.member_role_id = member_role_id_to_set
|
|
226
|
-
|
|
229
|
+
try:
|
|
230
|
+
group_member.save()
|
|
231
|
+
except GitlabError as e:
|
|
232
|
+
error(f"Could not save user {common_username}, error: {e.error_message}")
|
|
233
|
+
raise e
|
|
227
234
|
|
|
228
235
|
else:
|
|
229
|
-
|
|
230
|
-
"Adding user '%s' who previously was not a member.",
|
|
231
|
-
common_username,
|
|
232
|
-
)
|
|
236
|
+
verbose(f"Adding user {common_username} who previously was not a member.")
|
|
233
237
|
group.members.create(
|
|
234
238
|
{
|
|
235
239
|
"user_id": user_id,
|
|
@@ -259,7 +263,7 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
259
263
|
continue
|
|
260
264
|
|
|
261
265
|
if keep_bots and gl_user.bot:
|
|
262
|
-
|
|
266
|
+
verbose(f"Will not remove bot user '{user}' as the 'keep_bots' option is true.")
|
|
263
267
|
continue
|
|
264
268
|
|
|
265
269
|
try:
|
|
@@ -269,9 +273,9 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
269
273
|
raise delete_error
|
|
270
274
|
|
|
271
275
|
else:
|
|
272
|
-
|
|
276
|
+
verbose("Not enforcing group members.")
|
|
273
277
|
|
|
274
|
-
|
|
278
|
+
verbose(f"Group members AFTER: {group.members.list(get_all=True)}")
|
|
275
279
|
|
|
276
280
|
@staticmethod
|
|
277
281
|
def get_group_members(group) -> dict:
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from logging import info, debug, warning
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
from gitlabform.gitlab import GitLab
|
|
6
|
+
from gitlab.v4.objects.groups import Group
|
|
7
|
+
from gitlabform.processors.abstract_processor import AbstractProcessor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GroupSettingsProcessor(AbstractProcessor):
|
|
11
|
+
def __init__(self, gitlab: GitLab):
|
|
12
|
+
super().__init__("group_settings", gitlab)
|
|
13
|
+
|
|
14
|
+
def _process_configuration(self, group: str, configuration: Dict):
|
|
15
|
+
configured_group_settings = configuration.get("group_settings", {})
|
|
16
|
+
|
|
17
|
+
gitlab_group: Group = self.gl.get_group_by_path_cached(group)
|
|
18
|
+
|
|
19
|
+
# Remove avatar from config to process it last
|
|
20
|
+
avatar_config = configured_group_settings.pop("avatar", None)
|
|
21
|
+
|
|
22
|
+
# Process other settings first
|
|
23
|
+
if self._needs_update(gitlab_group.asdict(), configured_group_settings):
|
|
24
|
+
info(f"Updating group settings for group {gitlab_group.name}")
|
|
25
|
+
self.update_group_settings(gitlab_group, configured_group_settings)
|
|
26
|
+
else:
|
|
27
|
+
debug("No update needed for Group Settings")
|
|
28
|
+
|
|
29
|
+
# Process avatar last - with error handling that doesn't stop execution
|
|
30
|
+
if avatar_config is not None:
|
|
31
|
+
try:
|
|
32
|
+
self._process_group_avatar(gitlab_group, {"avatar": avatar_config})
|
|
33
|
+
except Exception as e:
|
|
34
|
+
warning(f"Failed to process group avatar: {e}")
|
|
35
|
+
raise e
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def update_group_settings(gitlab_group: Group, group_settings_config: dict):
|
|
39
|
+
for key in group_settings_config:
|
|
40
|
+
value = group_settings_config[key]
|
|
41
|
+
debug(f"Updating setting {key} to value {value}")
|
|
42
|
+
gitlab_group.__setattr__(key, value)
|
|
43
|
+
gitlab_group.save()
|
|
44
|
+
|
|
45
|
+
def _process_group_avatar(self, gitlab_group: Group, group_settings_config: dict) -> None:
|
|
46
|
+
"""Process group avatar settings from configuration."""
|
|
47
|
+
debug("Processing group avatar configuration")
|
|
48
|
+
|
|
49
|
+
avatar_path = group_settings_config.get("avatar")
|
|
50
|
+
if avatar_path is None:
|
|
51
|
+
debug("No avatar configuration provided, skipping avatar processing")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
debug(f"Avatar configuration found: {avatar_path}")
|
|
55
|
+
|
|
56
|
+
# Check current avatar status
|
|
57
|
+
current_avatar = getattr(gitlab_group, "avatar_url", None)
|
|
58
|
+
|
|
59
|
+
if avatar_path == "":
|
|
60
|
+
# Want to remove avatar
|
|
61
|
+
if not current_avatar:
|
|
62
|
+
debug("Avatar already empty, no update needed")
|
|
63
|
+
return
|
|
64
|
+
debug("Deleting group avatar")
|
|
65
|
+
gitlab_group.avatar = ""
|
|
66
|
+
gitlab_group.save()
|
|
67
|
+
debug("Avatar deleted successfully")
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
# Resolve relative paths to absolute paths
|
|
71
|
+
if not os.path.isabs(avatar_path):
|
|
72
|
+
# Convert relative path to absolute path relative to current working directory
|
|
73
|
+
avatar_path = os.path.abspath(avatar_path)
|
|
74
|
+
debug(f"Resolved relative path to absolute path: {avatar_path}")
|
|
75
|
+
|
|
76
|
+
# Want to set avatar from file
|
|
77
|
+
debug(f"Setting group avatar from file: {avatar_path}")
|
|
78
|
+
try:
|
|
79
|
+
with open(avatar_path, "rb") as avatar_file:
|
|
80
|
+
gitlab_group.avatar = avatar_file
|
|
81
|
+
gitlab_group.save()
|
|
82
|
+
debug("Group avatar uploaded successfully")
|
|
83
|
+
except FileNotFoundError:
|
|
84
|
+
error_msg = f"Group avatar file not found: {avatar_path}"
|
|
85
|
+
debug(error_msg)
|
|
86
|
+
raise FileNotFoundError(error_msg)
|
|
87
|
+
except Exception as e:
|
|
88
|
+
error_msg = f"Error uploading group avatar: {str(e)}"
|
|
89
|
+
debug(error_msg)
|
|
90
|
+
raise Exception(error_msg) from e
|
|
@@ -3,6 +3,7 @@ from typing import Dict, Any, List
|
|
|
3
3
|
|
|
4
4
|
from gitlab.base import RESTObject, RESTObjectList
|
|
5
5
|
from gitlab.v4.objects import Project
|
|
6
|
+
from gitlab.v4.objects import ProjectHook
|
|
6
7
|
|
|
7
8
|
from gitlabform.gitlab import GitLab
|
|
8
9
|
from gitlabform.processors.abstract_processor import AbstractProcessor
|
|
@@ -15,7 +16,7 @@ class HooksProcessor(AbstractProcessor):
|
|
|
15
16
|
def _process_configuration(self, project_and_group: str, configuration: dict):
|
|
16
17
|
debug("Processing hooks...")
|
|
17
18
|
project: Project = self.gl.get_project_by_path_cached(project_and_group)
|
|
18
|
-
project_hooks:
|
|
19
|
+
project_hooks: list[ProjectHook] = project.hooks.list(get_all=True)
|
|
19
20
|
|
|
20
21
|
hooks_in_config: tuple[str, ...] = tuple(x for x in sorted(configuration["hooks"]) if x != "enforce")
|
|
21
22
|
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/integrations_processor.py
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from cli_ui import debug as verbose
|
|
2
2
|
|
|
3
|
+
from gitlab.exceptions import GitlabDeleteError
|
|
3
4
|
from gitlab.v4.objects import Project, ProjectIntegration
|
|
4
5
|
from gitlabform.gitlab import GitLab
|
|
5
6
|
from gitlabform.processors.abstract_processor import AbstractProcessor
|
|
@@ -18,7 +19,15 @@ class IntegrationsProcessor(AbstractProcessor):
|
|
|
18
19
|
|
|
19
20
|
if configured_integrations[integration].get("delete"):
|
|
20
21
|
verbose(f"Deleting integration: {integration}")
|
|
21
|
-
|
|
22
|
+
try:
|
|
23
|
+
gl_integration.delete()
|
|
24
|
+
except GitlabDeleteError as e:
|
|
25
|
+
# If we get a 404 the integration does not exist, so we can ignore the error
|
|
26
|
+
if e.response_code == 404:
|
|
27
|
+
verbose(f"Integration {integration} does not exist, skipping deletion.")
|
|
28
|
+
else:
|
|
29
|
+
verbose(f"Failed to delete integration {integration}: {e}")
|
|
30
|
+
raise
|
|
22
31
|
else:
|
|
23
32
|
verbose(f"Setting integration: {integration}")
|
|
24
33
|
project.integrations.update(integration, configured_integrations[integration])
|
|
@@ -49,15 +49,10 @@ class MembersProcessor(AbstractProcessor):
|
|
|
49
49
|
and expires_at == current_groups[common_group_name]["expires_at"]
|
|
50
50
|
and access_level == current_groups[common_group_name]["group_access_level"]
|
|
51
51
|
):
|
|
52
|
-
verbose(
|
|
53
|
-
|
|
54
|
-
common_group_name,
|
|
55
|
-
)
|
|
56
|
-
verbose(
|
|
57
|
-
"Current settings for '%s' are: %s" % (common_group_name, current_groups[common_group_name])
|
|
58
|
-
)
|
|
52
|
+
verbose(f"Ignoring group '{common_group_name}' as it is already a member")
|
|
53
|
+
verbose(f"Current settings for '{common_group_name}' are: {current_groups[common_group_name]}")
|
|
59
54
|
else:
|
|
60
|
-
verbose("Setting group '
|
|
55
|
+
verbose(f"Setting group '{common_group_name}' as a member")
|
|
61
56
|
access = access_level
|
|
62
57
|
expiry = expires_at
|
|
63
58
|
|
|
@@ -86,7 +81,6 @@ class MembersProcessor(AbstractProcessor):
|
|
|
86
81
|
enforce_members: bool,
|
|
87
82
|
keep_bots: bool,
|
|
88
83
|
):
|
|
89
|
-
|
|
90
84
|
project: Project = self.gl.get_project_by_path_cached(project_and_group)
|
|
91
85
|
|
|
92
86
|
current_members = self._get_members_from_project(project)
|
|
@@ -97,10 +91,11 @@ class MembersProcessor(AbstractProcessor):
|
|
|
97
91
|
for user in users:
|
|
98
92
|
info(f"Processing user '{user}'...")
|
|
99
93
|
|
|
100
|
-
|
|
101
|
-
if
|
|
94
|
+
gitlab_user = self.gl.get_user_by_username_cached(user)
|
|
95
|
+
if gitlab_user is None:
|
|
102
96
|
warning(f"Could not find user '{user}' in Gitlab, skipping...")
|
|
103
97
|
continue
|
|
98
|
+
user_id = gitlab_user.id
|
|
104
99
|
|
|
105
100
|
expires_at = users[user]["expires_at"].strftime("%Y-%m-%d") if "expires_at" in users[user] else None
|
|
106
101
|
access_level = users[user]["access_level"] if "access_level" in users[user] else None
|
|
@@ -128,33 +123,23 @@ class MembersProcessor(AbstractProcessor):
|
|
|
128
123
|
and access_level == current_member.access_level
|
|
129
124
|
and member_role_id == member_role_id_before
|
|
130
125
|
):
|
|
131
|
-
verbose(
|
|
132
|
-
|
|
133
|
-
common_username,
|
|
134
|
-
)
|
|
135
|
-
verbose(
|
|
136
|
-
"Current settings for '%s' are: %s" % (common_username, current_members[common_username])
|
|
137
|
-
)
|
|
126
|
+
verbose(f"Nothing to change for user '{common_username}' - same config now as to set.")
|
|
127
|
+
verbose(f"Current settings for '{common_username}' are: {current_members[common_username]}")
|
|
138
128
|
else:
|
|
139
129
|
verbose(
|
|
140
|
-
"Editing user '
|
|
141
|
-
common_username,
|
|
130
|
+
f"Editing user '{common_username}' membership to change their access level or expires at",
|
|
142
131
|
)
|
|
143
|
-
update_data = {
|
|
144
|
-
"user_id": common_username,
|
|
145
|
-
"access_level": access_level,
|
|
146
|
-
"member_role_id": member_role_id_before,
|
|
147
|
-
}
|
|
148
132
|
|
|
133
|
+
project_member = project.members.get(id=user_id)
|
|
134
|
+
project_member.access_level = access_level
|
|
135
|
+
project_member.member_role_id = member_role_id_before
|
|
149
136
|
if expires_at:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
project.members.update(new_data=update_data)
|
|
137
|
+
project_member.expires_at = expires_at
|
|
138
|
+
project_member.save()
|
|
153
139
|
|
|
154
140
|
else:
|
|
155
141
|
verbose(
|
|
156
|
-
"Adding user '
|
|
157
|
-
common_username,
|
|
142
|
+
f"Adding user '{common_username}' who previously was not a member.",
|
|
158
143
|
)
|
|
159
144
|
create_data = {
|
|
160
145
|
"user_id": user_id,
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/project_settings_processor.py
RENAMED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import os
|
|
2
|
+
from logging import debug, error, warning
|
|
2
3
|
from typing import Callable, Dict, List
|
|
3
4
|
|
|
4
5
|
from gitlab.v4.objects import Project
|
|
@@ -24,6 +25,10 @@ class ProjectSettingsProcessor(AbstractProcessor):
|
|
|
24
25
|
|
|
25
26
|
self._process_project_topics(project_settings_in_config, project_settings_in_gitlab)
|
|
26
27
|
|
|
28
|
+
# Remove avatar from config to process it last
|
|
29
|
+
avatar_config = project_settings_in_config.pop("avatar", None)
|
|
30
|
+
|
|
31
|
+
# Process other settings first
|
|
27
32
|
if self._needs_update(project_settings_in_gitlab, project_settings_in_config):
|
|
28
33
|
debug("Updating project settings")
|
|
29
34
|
for key, value in project_settings_in_config.items():
|
|
@@ -37,7 +42,16 @@ class ProjectSettingsProcessor(AbstractProcessor):
|
|
|
37
42
|
else:
|
|
38
43
|
debug("No update needed for project settings")
|
|
39
44
|
|
|
45
|
+
# Process avatar last - with error handling that doesn't stop execution
|
|
46
|
+
if avatar_config is not None:
|
|
47
|
+
try:
|
|
48
|
+
self._process_project_avatar(project, {"avatar": avatar_config})
|
|
49
|
+
except Exception as e:
|
|
50
|
+
warning(f"Failed to process project avatar: {e}")
|
|
51
|
+
raise e
|
|
52
|
+
|
|
40
53
|
def get_project_settings(self, project_path: str):
|
|
54
|
+
"""Get project settings from GitLab."""
|
|
41
55
|
return self.gl.get_project_by_path_cached(project_path).asdict()
|
|
42
56
|
|
|
43
57
|
def _print_diff(self, project_or_project_and_group: str, entity_config, diff_only_changed: bool):
|
|
@@ -94,3 +108,50 @@ class ProjectSettingsProcessor(AbstractProcessor):
|
|
|
94
108
|
debug(f"topics after adjustment: {adjusted_project_topics_to_set}")
|
|
95
109
|
|
|
96
110
|
project_settings_in_config["topics"] = adjusted_project_topics_to_set
|
|
111
|
+
|
|
112
|
+
def _process_project_avatar(self, project: Project, project_settings_in_config: dict) -> None:
|
|
113
|
+
"""Process project avatar settings from configuration."""
|
|
114
|
+
debug("Processing project avatar configuration")
|
|
115
|
+
|
|
116
|
+
avatar_path = project_settings_in_config.get("avatar")
|
|
117
|
+
if avatar_path is None:
|
|
118
|
+
debug("No avatar configuration provided, skipping avatar processing")
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
debug(f"Avatar configuration found: {avatar_path}")
|
|
122
|
+
|
|
123
|
+
# Check current avatar status
|
|
124
|
+
current_avatar = getattr(project, "avatar_url", None)
|
|
125
|
+
|
|
126
|
+
if avatar_path == "":
|
|
127
|
+
# Want to remove avatar
|
|
128
|
+
if not current_avatar:
|
|
129
|
+
debug("Avatar already empty, no update needed")
|
|
130
|
+
return
|
|
131
|
+
debug("Deleting project avatar")
|
|
132
|
+
project.avatar = ""
|
|
133
|
+
project.save()
|
|
134
|
+
debug("Project avatar deleted successfully")
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
# Resolve relative paths to absolute paths
|
|
138
|
+
if not os.path.isabs(avatar_path):
|
|
139
|
+
# Convert relative path to absolute path relative to current working directory
|
|
140
|
+
avatar_path = os.path.abspath(avatar_path)
|
|
141
|
+
debug(f"Resolved relative path to absolute path: {avatar_path}")
|
|
142
|
+
|
|
143
|
+
# Want to set avatar from file
|
|
144
|
+
debug(f"Setting project avatar from file: {avatar_path}")
|
|
145
|
+
try:
|
|
146
|
+
with open(avatar_path, "rb") as avatar_file:
|
|
147
|
+
project.avatar = avatar_file
|
|
148
|
+
project.save()
|
|
149
|
+
debug("Project avatar uploaded successfully")
|
|
150
|
+
except FileNotFoundError:
|
|
151
|
+
error_msg = f"Project avatar file not found: {avatar_path}"
|
|
152
|
+
error(error_msg)
|
|
153
|
+
raise FileNotFoundError(error_msg)
|
|
154
|
+
except Exception as e:
|
|
155
|
+
error_msg = f"Error uploading project avatar: {str(e)}"
|
|
156
|
+
error(error_msg)
|
|
157
|
+
raise Exception(error_msg) from e
|
|
@@ -24,7 +24,7 @@ class SchedulesProcessor(AbstractProcessor):
|
|
|
24
24
|
configured_schedules.pop("enforce")
|
|
25
25
|
|
|
26
26
|
project: Project = self.gl.get_project_by_path_cached(project_and_group)
|
|
27
|
-
existing_schedules:
|
|
27
|
+
existing_schedules: list[ProjectPipelineSchedule] = project.pipelineschedules.list(get_all=True)
|
|
28
28
|
|
|
29
29
|
schedule_ids_by_description: Dict = self._group_schedule_ids_by_description(existing_schedules)
|
|
30
30
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from cli_ui import debug as verbose, info, warning
|
|
2
|
-
from typing import Dict, List, Callable
|
|
2
|
+
from typing import Dict, List, Callable, Union
|
|
3
3
|
|
|
4
4
|
from gitlab.base import RESTObjectList, RESTObject
|
|
5
5
|
from gitlab.v4.objects import Group, Project, ProjectLabel, GroupLabel
|
|
@@ -103,7 +103,7 @@ class LabelsProcessor:
|
|
|
103
103
|
group_or_project: Group | Project,
|
|
104
104
|
label_key: str,
|
|
105
105
|
parent_object_type: str,
|
|
106
|
-
existing_group_and_parent_labels:
|
|
106
|
+
existing_group_and_parent_labels: Union[List[GroupLabel], List[ProjectLabel]],
|
|
107
107
|
):
|
|
108
108
|
label = configured_labels.get(label_key)
|
|
109
109
|
configured_label_name = label.get("name")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitlabform
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.0
|
|
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/
|
|
@@ -20,7 +20,7 @@ Classifier: Topic :: Software Development :: Version Control :: Git
|
|
|
20
20
|
Requires-Python: >=3.12.0
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: certifi==2025.
|
|
23
|
+
Requires-Dist: certifi==2025.7.14
|
|
24
24
|
Requires-Dist: cli-ui==0.19.0
|
|
25
25
|
Requires-Dist: ez-yaml==1.2.0
|
|
26
26
|
Requires-Dist: Jinja2==3.1.6
|
|
@@ -28,23 +28,23 @@ Requires-Dist: luddite==1.0.4
|
|
|
28
28
|
Requires-Dist: MarkupSafe==3.0.2
|
|
29
29
|
Requires-Dist: mergedeep==1.3.4
|
|
30
30
|
Requires-Dist: packaging==25.0
|
|
31
|
-
Requires-Dist: python-gitlab==
|
|
32
|
-
Requires-Dist: python-gitlab[graphql]==
|
|
33
|
-
Requires-Dist: requests==2.32.
|
|
31
|
+
Requires-Dist: python-gitlab==6.1.0
|
|
32
|
+
Requires-Dist: python-gitlab[graphql]==6.1.0
|
|
33
|
+
Requires-Dist: requests==2.32.4
|
|
34
34
|
Requires-Dist: ruamel.yaml==0.17.21
|
|
35
|
-
Requires-Dist: types-requests==2.32.
|
|
36
|
-
Requires-Dist: types-setuptools==
|
|
35
|
+
Requires-Dist: types-requests==2.32.4.20250611
|
|
36
|
+
Requires-Dist: types-setuptools==80.9.0.20250529
|
|
37
37
|
Requires-Dist: yamlpath==3.8.2
|
|
38
38
|
Provides-Extra: test
|
|
39
|
-
Requires-Dist: coverage==7.
|
|
40
|
-
Requires-Dist: cryptography==
|
|
41
|
-
Requires-Dist: deepdiff==8.
|
|
42
|
-
Requires-Dist: mypy==1.
|
|
43
|
-
Requires-Dist: mypy-extensions==1.
|
|
39
|
+
Requires-Dist: coverage==7.9.2; extra == "test"
|
|
40
|
+
Requires-Dist: cryptography==45.0.5; extra == "test"
|
|
41
|
+
Requires-Dist: deepdiff==8.5.0; extra == "test"
|
|
42
|
+
Requires-Dist: mypy==1.17.0; extra == "test"
|
|
43
|
+
Requires-Dist: mypy-extensions==1.1.0; extra == "test"
|
|
44
44
|
Requires-Dist: pre-commit==2.21.0; extra == "test"
|
|
45
|
-
Requires-Dist: pytest==8.
|
|
46
|
-
Requires-Dist: pytest-cov==6.
|
|
47
|
-
Requires-Dist: pytest-rerunfailures==15.
|
|
45
|
+
Requires-Dist: pytest==8.4.1; extra == "test"
|
|
46
|
+
Requires-Dist: pytest-cov==6.2.1; extra == "test"
|
|
47
|
+
Requires-Dist: pytest-rerunfailures==15.1; extra == "test"
|
|
48
48
|
Requires-Dist: xkcdpass==1.20.0; extra == "test"
|
|
49
49
|
Provides-Extra: docs
|
|
50
50
|
Requires-Dist: mkdocs; extra == "docs"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
certifi==2025.7.14
|
|
2
|
+
cli-ui==0.19.0
|
|
3
|
+
ez-yaml==1.2.0
|
|
4
|
+
Jinja2==3.1.6
|
|
5
|
+
luddite==1.0.4
|
|
6
|
+
MarkupSafe==3.0.2
|
|
7
|
+
mergedeep==1.3.4
|
|
8
|
+
packaging==25.0
|
|
9
|
+
python-gitlab==6.1.0
|
|
10
|
+
python-gitlab[graphql]==6.1.0
|
|
11
|
+
requests==2.32.4
|
|
12
|
+
ruamel.yaml==0.17.21
|
|
13
|
+
types-requests==2.32.4.20250611
|
|
14
|
+
types-setuptools==80.9.0.20250529
|
|
15
|
+
yamlpath==3.8.2
|
|
16
|
+
|
|
17
|
+
[docs]
|
|
18
|
+
mkdocs
|
|
19
|
+
mkdocs-material
|
|
20
|
+
|
|
21
|
+
[test]
|
|
22
|
+
coverage==7.9.2
|
|
23
|
+
cryptography==45.0.5
|
|
24
|
+
deepdiff==8.5.0
|
|
25
|
+
mypy==1.17.0
|
|
26
|
+
mypy-extensions==1.1.0
|
|
27
|
+
pre-commit==2.21.0
|
|
28
|
+
pytest==8.4.1
|
|
29
|
+
pytest-cov==6.2.1
|
|
30
|
+
pytest-rerunfailures==15.1
|
|
31
|
+
xkcdpass==1.20.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gitlabform"
|
|
7
|
-
version = "4.
|
|
7
|
+
version = "4.3.0"
|
|
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"]
|
|
@@ -23,7 +23,7 @@ classifiers = [
|
|
|
23
23
|
]
|
|
24
24
|
requires-python = ">=3.12.0"
|
|
25
25
|
dependencies = [
|
|
26
|
-
"certifi==2025.
|
|
26
|
+
"certifi==2025.7.14",
|
|
27
27
|
"cli-ui==0.19.0",
|
|
28
28
|
"ez-yaml==1.2.0",
|
|
29
29
|
"Jinja2==3.1.6",
|
|
@@ -31,12 +31,12 @@ dependencies = [
|
|
|
31
31
|
"MarkupSafe==3.0.2",
|
|
32
32
|
"mergedeep==1.3.4",
|
|
33
33
|
"packaging==25.0",
|
|
34
|
-
"python-gitlab==
|
|
35
|
-
"python-gitlab[graphql]==
|
|
36
|
-
"requests==2.32.
|
|
34
|
+
"python-gitlab==6.1.0",
|
|
35
|
+
"python-gitlab[graphql]==6.1.0",
|
|
36
|
+
"requests==2.32.4",
|
|
37
37
|
"ruamel.yaml==0.17.21",
|
|
38
|
-
"types-requests==2.32.
|
|
39
|
-
"types-setuptools==
|
|
38
|
+
"types-requests==2.32.4.20250611",
|
|
39
|
+
"types-setuptools==80.9.0.20250529",
|
|
40
40
|
"yamlpath==3.8.2",
|
|
41
41
|
]
|
|
42
42
|
|
|
@@ -52,15 +52,15 @@ content-type = "text/markdown"
|
|
|
52
52
|
|
|
53
53
|
[project.optional-dependencies]
|
|
54
54
|
test = [
|
|
55
|
-
"coverage==7.
|
|
56
|
-
"cryptography==
|
|
57
|
-
"deepdiff==8.
|
|
58
|
-
"mypy==1.
|
|
59
|
-
"mypy-extensions==1.
|
|
55
|
+
"coverage==7.9.2",
|
|
56
|
+
"cryptography==45.0.5",
|
|
57
|
+
"deepdiff==8.5.0",
|
|
58
|
+
"mypy==1.17.0",
|
|
59
|
+
"mypy-extensions==1.1.0",
|
|
60
60
|
"pre-commit==2.21.0",
|
|
61
|
-
"pytest==8.
|
|
62
|
-
"pytest-cov==6.
|
|
63
|
-
"pytest-rerunfailures==15.
|
|
61
|
+
"pytest==8.4.1",
|
|
62
|
+
"pytest-cov==6.2.1",
|
|
63
|
+
"pytest-rerunfailures==15.1",
|
|
64
64
|
"xkcdpass==1.20.0",
|
|
65
65
|
]
|
|
66
66
|
docs = ["mkdocs", "mkdocs-material"]
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
from logging import info, debug
|
|
2
|
-
from typing import Dict
|
|
3
|
-
|
|
4
|
-
from gitlabform.gitlab import GitLab
|
|
5
|
-
from gitlab.v4.objects.groups import Group
|
|
6
|
-
from gitlabform.processors.abstract_processor import AbstractProcessor
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class GroupSettingsProcessor(AbstractProcessor):
|
|
10
|
-
def __init__(self, gitlab: GitLab):
|
|
11
|
-
super().__init__("group_settings", gitlab)
|
|
12
|
-
|
|
13
|
-
def _process_configuration(self, group: str, configuration: Dict):
|
|
14
|
-
configured_group_settings = configuration.get("group_settings", {})
|
|
15
|
-
|
|
16
|
-
gitlab_group: Group = self.gl.get_group_by_path_cached(group)
|
|
17
|
-
|
|
18
|
-
if self._needs_update(gitlab_group.asdict(), configured_group_settings):
|
|
19
|
-
info(f"Updating group settings for group {gitlab_group.name}")
|
|
20
|
-
self.update_group_settings(gitlab_group, configured_group_settings)
|
|
21
|
-
else:
|
|
22
|
-
debug("No update needed for Group Settings")
|
|
23
|
-
|
|
24
|
-
@staticmethod
|
|
25
|
-
def update_group_settings(gitlab_group: Group, group_settings_config: dict):
|
|
26
|
-
for key in group_settings_config:
|
|
27
|
-
value = group_settings_config[key]
|
|
28
|
-
debug(f"Updating setting {key} to value {value}")
|
|
29
|
-
gitlab_group.__setattr__(key, value)
|
|
30
|
-
gitlab_group.save()
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
certifi==2025.1.31
|
|
2
|
-
cli-ui==0.19.0
|
|
3
|
-
ez-yaml==1.2.0
|
|
4
|
-
Jinja2==3.1.6
|
|
5
|
-
luddite==1.0.4
|
|
6
|
-
MarkupSafe==3.0.2
|
|
7
|
-
mergedeep==1.3.4
|
|
8
|
-
packaging==25.0
|
|
9
|
-
python-gitlab==5.6.0
|
|
10
|
-
python-gitlab[graphql]==5.6.0
|
|
11
|
-
requests==2.32.3
|
|
12
|
-
ruamel.yaml==0.17.21
|
|
13
|
-
types-requests==2.32.0.20250328
|
|
14
|
-
types-setuptools==78.1.0.20250329
|
|
15
|
-
yamlpath==3.8.2
|
|
16
|
-
|
|
17
|
-
[docs]
|
|
18
|
-
mkdocs
|
|
19
|
-
mkdocs-material
|
|
20
|
-
|
|
21
|
-
[test]
|
|
22
|
-
coverage==7.8.0
|
|
23
|
-
cryptography==44.0.2
|
|
24
|
-
deepdiff==8.4.2
|
|
25
|
-
mypy==1.15.0
|
|
26
|
-
mypy-extensions==1.0.0
|
|
27
|
-
pre-commit==2.21.0
|
|
28
|
-
pytest==8.3.5
|
|
29
|
-
pytest-cov==6.1.1
|
|
30
|
-
pytest-rerunfailures==15.0
|
|
31
|
-
xkcdpass==1.20.0
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/group/group_ldap_links_processor.py
RENAMED
|
File without changes
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/group/group_push_rules_processor.py
RENAMED
|
File without changes
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/group/group_saml_links_processor.py
RENAMED
|
File without changes
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/group/group_variables_processor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/deploy_keys_processor.py
RENAMED
|
File without changes
|
|
File without changes
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/job_token_scope_processor.py
RENAMED
|
File without changes
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/merge_requests_approval_rules.py
RENAMED
|
File without changes
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/merge_requests_approvals.py
RENAMED
|
File without changes
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/project_labels_processor.py
RENAMED
|
File without changes
|
|
File without changes
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/project_push_rules_processor.py
RENAMED
|
File without changes
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/gitlabform/processors/project/project_security_settings.py
RENAMED
|
File without changes
|
{gitlabform-4.2.5 → gitlabform-4.3.0}/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
|