gitlabform 4.2.6__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.6 → gitlabform-4.3.0}/PKG-INFO +3 -3
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/group/group_members_processor.py +25 -28
- gitlabform-4.3.0/gitlabform/processors/group/group_settings_processor.py +90 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/project_settings_processor.py +62 -1
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform.egg-info/PKG-INFO +3 -3
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform.egg-info/requires.txt +2 -2
- {gitlabform-4.2.6 → gitlabform-4.3.0}/pyproject.toml +3 -3
- gitlabform-4.2.6/gitlabform/processors/group/group_settings_processor.py +0 -30
- {gitlabform-4.2.6 → gitlabform-4.3.0}/LICENSE +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/README.md +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/__init__.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/configuration/__init__.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/configuration/common.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/configuration/core.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/configuration/groups.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/configuration/projects.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/configuration/transform.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/constants.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/__init__.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/commits.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/core.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/group_badges.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/group_ldap_links.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/group_variables.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/groups.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/merge_requests.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/pipelines.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/project_badges.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/project_deploy_keys.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/project_merge_requests_approvals.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/project_protected_environments.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/project_security_settings.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/projects.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/python_gitlab.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/gitlab/variables.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/lists/__init__.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/lists/filter.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/lists/groups.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/lists/projects.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/output.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/__init__.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/abstract_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/application/__init__.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/application/application_settings_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/defining_keys.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/group/__init__.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/group/group_badges_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/group/group_hooks_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/group/group_labels_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/group/group_ldap_links_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/group/group_push_rules_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/group/group_saml_links_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/group/group_variables_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/multiple_entities_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/__init__.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/badges_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/branches_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/deploy_keys_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/files_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/hooks_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/integrations_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/job_token_scope_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/members_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/merge_requests_approval_rules.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/merge_requests_approvals.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/project_labels_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/project_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/project_push_rules_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/project_security_settings.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/resource_groups_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/schedules_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/tags_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/variables_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/shared/__init__.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/shared/protected_environments_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/single_entity_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/util/__init__.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/util/decorators.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/util/difference_logger.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/util/labels_processor.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/run.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/util.py +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform.egg-info/SOURCES.txt +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform.egg-info/dependency_links.txt +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform.egg-info/entry_points.txt +0 -0
- {gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform.egg-info/top_level.txt +0 -0
- {gitlabform-4.2.6 → 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
|
|
@@ -39,7 +39,7 @@ Provides-Extra: test
|
|
|
39
39
|
Requires-Dist: coverage==7.9.2; extra == "test"
|
|
40
40
|
Requires-Dist: cryptography==45.0.5; extra == "test"
|
|
41
41
|
Requires-Dist: deepdiff==8.5.0; extra == "test"
|
|
42
|
-
Requires-Dist: mypy==1.
|
|
42
|
+
Requires-Dist: mypy==1.17.0; extra == "test"
|
|
43
43
|
Requires-Dist: mypy-extensions==1.1.0; extra == "test"
|
|
44
44
|
Requires-Dist: pre-commit==2.21.0; extra == "test"
|
|
45
45
|
Requires-Dist: pytest==8.4.1; extra == "test"
|
{gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/group/group_members_processor.py
RENAMED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
from logging import debug, error
|
|
2
1
|
from typing import Dict, Tuple
|
|
3
2
|
|
|
4
|
-
from cli_ui import fatal, error, debug as verbose
|
|
3
|
+
from cli_ui import info, fatal, error, debug as verbose
|
|
5
4
|
|
|
6
5
|
from gitlabform.constants import EXIT_INVALID_INPUT
|
|
7
6
|
from gitlabform.gitlab import GitLab, AccessLevel
|
|
@@ -67,7 +66,7 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
67
66
|
enforce_group_members: bool,
|
|
68
67
|
):
|
|
69
68
|
shared_with_groups_before = group_being_processed.shared_with_groups
|
|
70
|
-
|
|
69
|
+
verbose("Group shared with BEFORE: %s", shared_with_groups_before)
|
|
71
70
|
|
|
72
71
|
groups_before_by_group_path = dict()
|
|
73
72
|
for shared_with_group in shared_with_groups_before:
|
|
@@ -87,15 +86,12 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
87
86
|
expires_at_before = groups_before_by_group_path[share_with_group_path]["expires_at"]
|
|
88
87
|
|
|
89
88
|
if group_access_before == group_access_to_set and expires_at_before == expires_at_to_set:
|
|
90
|
-
|
|
89
|
+
verbose(
|
|
91
90
|
"Nothing to change for group '%s' - same config now as to set.",
|
|
92
91
|
share_with_group_path,
|
|
93
92
|
)
|
|
94
93
|
else:
|
|
95
|
-
|
|
96
|
-
"Re-adding group '%s' to change their access level or expires at.",
|
|
97
|
-
share_with_group_path,
|
|
98
|
-
)
|
|
94
|
+
info(f"Re-adding group {share_with_group_path} to change their access level or expires at.")
|
|
99
95
|
share_with_group_id = groups_before_by_group_path[share_with_group_path]["group_id"]
|
|
100
96
|
# we will remove the group first and then re-add them,
|
|
101
97
|
# to ensure that the group has the expected access level
|
|
@@ -108,9 +104,8 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
108
104
|
raise e
|
|
109
105
|
|
|
110
106
|
else:
|
|
111
|
-
|
|
112
|
-
"Adding group
|
|
113
|
-
share_with_group_path,
|
|
107
|
+
verbose(
|
|
108
|
+
f"Adding group {share_with_group_path} who previously was not a member.",
|
|
114
109
|
)
|
|
115
110
|
|
|
116
111
|
share_with_group_id = self.gl.get_group_id(share_with_group_path)
|
|
@@ -124,16 +119,16 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
124
119
|
# remove groups not configured explicitly
|
|
125
120
|
groups_not_configured = set(groups_before_by_group_path) - set(groups_to_share_with_by_path)
|
|
126
121
|
for group_path in groups_not_configured:
|
|
127
|
-
|
|
122
|
+
verbose(
|
|
128
123
|
"Removing group '%s' who is not configured to be a member.",
|
|
129
124
|
group_path,
|
|
130
125
|
)
|
|
131
126
|
share_with_group_id = self.gl.get_group_id(group_path)
|
|
132
127
|
self._unshare(group_being_processed, share_with_group_id)
|
|
133
128
|
else:
|
|
134
|
-
|
|
129
|
+
verbose("Not enforcing group members.")
|
|
135
130
|
|
|
136
|
-
|
|
131
|
+
verbose(
|
|
137
132
|
"Group shared with AFTER: %s",
|
|
138
133
|
group_being_processed.members.list(get_all=True),
|
|
139
134
|
)
|
|
@@ -143,7 +138,7 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
143
138
|
try:
|
|
144
139
|
group_being_processed.unshare(share_with_group_id)
|
|
145
140
|
except GitlabDeleteError:
|
|
146
|
-
|
|
141
|
+
info(f"Group with id {share_with_group_id} could not be unshared, likely was never shared to begin with")
|
|
147
142
|
pass
|
|
148
143
|
|
|
149
144
|
def _process_users(
|
|
@@ -157,7 +152,7 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
157
152
|
# (note: we DON'T get inherited users as we don't manage them at this level anyway)
|
|
158
153
|
users_before = self.get_group_members(group)
|
|
159
154
|
|
|
160
|
-
|
|
155
|
+
verbose("Group members BEFORE: %s", users_before.keys())
|
|
161
156
|
|
|
162
157
|
if users_to_set_by_username:
|
|
163
158
|
# group users to set by access level
|
|
@@ -217,26 +212,28 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
217
212
|
and expires_at_before == expires_at_to_set
|
|
218
213
|
and member_role_id_before == member_role_id_to_set
|
|
219
214
|
):
|
|
220
|
-
|
|
215
|
+
verbose(
|
|
221
216
|
"Nothing to change for user '%s' - same config now as to set.",
|
|
222
217
|
common_username,
|
|
223
218
|
)
|
|
224
219
|
else:
|
|
225
|
-
|
|
226
|
-
"Editing user
|
|
227
|
-
|
|
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}."
|
|
228
224
|
)
|
|
229
225
|
|
|
230
226
|
group_member.access_level = access_level_to_set
|
|
231
227
|
group_member.expires_at = expires_at_to_set
|
|
232
228
|
group_member.member_role_id = member_role_id_to_set
|
|
233
|
-
|
|
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
|
|
234
234
|
|
|
235
235
|
else:
|
|
236
|
-
|
|
237
|
-
"Adding user '%s' who previously was not a member.",
|
|
238
|
-
common_username,
|
|
239
|
-
)
|
|
236
|
+
verbose(f"Adding user {common_username} who previously was not a member.")
|
|
240
237
|
group.members.create(
|
|
241
238
|
{
|
|
242
239
|
"user_id": user_id,
|
|
@@ -266,7 +263,7 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
266
263
|
continue
|
|
267
264
|
|
|
268
265
|
if keep_bots and gl_user.bot:
|
|
269
|
-
|
|
266
|
+
verbose(f"Will not remove bot user '{user}' as the 'keep_bots' option is true.")
|
|
270
267
|
continue
|
|
271
268
|
|
|
272
269
|
try:
|
|
@@ -276,9 +273,9 @@ class GroupMembersProcessor(AbstractProcessor):
|
|
|
276
273
|
raise delete_error
|
|
277
274
|
|
|
278
275
|
else:
|
|
279
|
-
|
|
276
|
+
verbose("Not enforcing group members.")
|
|
280
277
|
|
|
281
|
-
|
|
278
|
+
verbose(f"Group members AFTER: {group.members.list(get_all=True)}")
|
|
282
279
|
|
|
283
280
|
@staticmethod
|
|
284
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
|
{gitlabform-4.2.6 → 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
|
|
@@ -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
|
|
@@ -39,7 +39,7 @@ Provides-Extra: test
|
|
|
39
39
|
Requires-Dist: coverage==7.9.2; extra == "test"
|
|
40
40
|
Requires-Dist: cryptography==45.0.5; extra == "test"
|
|
41
41
|
Requires-Dist: deepdiff==8.5.0; extra == "test"
|
|
42
|
-
Requires-Dist: mypy==1.
|
|
42
|
+
Requires-Dist: mypy==1.17.0; extra == "test"
|
|
43
43
|
Requires-Dist: mypy-extensions==1.1.0; extra == "test"
|
|
44
44
|
Requires-Dist: pre-commit==2.21.0; extra == "test"
|
|
45
45
|
Requires-Dist: pytest==8.4.1; extra == "test"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
certifi==2025.
|
|
1
|
+
certifi==2025.7.14
|
|
2
2
|
cli-ui==0.19.0
|
|
3
3
|
ez-yaml==1.2.0
|
|
4
4
|
Jinja2==3.1.6
|
|
@@ -22,7 +22,7 @@ mkdocs-material
|
|
|
22
22
|
coverage==7.9.2
|
|
23
23
|
cryptography==45.0.5
|
|
24
24
|
deepdiff==8.5.0
|
|
25
|
-
mypy==1.
|
|
25
|
+
mypy==1.17.0
|
|
26
26
|
mypy-extensions==1.1.0
|
|
27
27
|
pre-commit==2.21.0
|
|
28
28
|
pytest==8.4.1
|
|
@@ -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",
|
|
@@ -55,7 +55,7 @@ test = [
|
|
|
55
55
|
"coverage==7.9.2",
|
|
56
56
|
"cryptography==45.0.5",
|
|
57
57
|
"deepdiff==8.5.0",
|
|
58
|
-
"mypy==1.
|
|
58
|
+
"mypy==1.17.0",
|
|
59
59
|
"mypy-extensions==1.1.0",
|
|
60
60
|
"pre-commit==2.21.0",
|
|
61
61
|
"pytest==8.4.1",
|
|
@@ -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()
|
|
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
|
|
File without changes
|
|
File without changes
|
{gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/group/group_ldap_links_processor.py
RENAMED
|
File without changes
|
{gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/group/group_push_rules_processor.py
RENAMED
|
File without changes
|
{gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/group/group_saml_links_processor.py
RENAMED
|
File without changes
|
{gitlabform-4.2.6 → 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.6 → gitlabform-4.3.0}/gitlabform/processors/project/deploy_keys_processor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/integrations_processor.py
RENAMED
|
File without changes
|
{gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/job_token_scope_processor.py
RENAMED
|
File without changes
|
|
File without changes
|
{gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/merge_requests_approval_rules.py
RENAMED
|
File without changes
|
{gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/merge_requests_approvals.py
RENAMED
|
File without changes
|
{gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/project_labels_processor.py
RENAMED
|
File without changes
|
|
File without changes
|
{gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/project_push_rules_processor.py
RENAMED
|
File without changes
|
{gitlabform-4.2.6 → gitlabform-4.3.0}/gitlabform/processors/project/project_security_settings.py
RENAMED
|
File without changes
|
{gitlabform-4.2.6 → 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
|
|
File without changes
|
|
File without changes
|