qontract-reconcile 0.10.1rc1179__py3-none-any.whl → 0.10.1rc1181__py3-none-any.whl
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.
- {qontract_reconcile-0.10.1rc1179.dist-info → qontract_reconcile-0.10.1rc1181.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc1179.dist-info → qontract_reconcile-0.10.1rc1181.dist-info}/RECORD +10 -10
- reconcile/gitlab_members.py +119 -122
- reconcile/terraform_repo.py +5 -15
- reconcile/test/test_gitlab_members.py +118 -211
- reconcile/test/test_terraform_repo.py +0 -26
- reconcile/utils/gitlab_api.py +23 -38
- {qontract_reconcile-0.10.1rc1179.dist-info → qontract_reconcile-0.10.1rc1181.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc1179.dist-info → qontract_reconcile-0.10.1rc1181.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc1179.dist-info → qontract_reconcile-0.10.1rc1181.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc1179.dist-info → qontract_reconcile-0.10.1rc1181.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.1rc1181
|
4
4
|
Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
|
5
5
|
Home-page: https://github.com/app-sre/qontract-reconcile
|
6
6
|
Author: Red Hat App-SRE Team
|
{qontract_reconcile-0.10.1rc1179.dist-info → qontract_reconcile-0.10.1rc1181.dist-info}/RECORD
RENAMED
@@ -32,7 +32,7 @@ reconcile/github_validator.py,sha256=cVTVxJIGR4a1Jz8wrdXEAb_CMpXUzvykVmUURX4cook
|
|
32
32
|
reconcile/gitlab_fork_compliance.py,sha256=c7UfqSAsW04c1bWJmXXaQDwtUcG4Kb6nCJAyRU2uAuw,4449
|
33
33
|
reconcile/gitlab_housekeeping.py,sha256=D0DOqC-xuMBMct04_MI8Lq32OAi_QMvvGLOz_E-77Dw,22482
|
34
34
|
reconcile/gitlab_labeler.py,sha256=4xJHmVX155fclrHqkR926sL1GH6RTN5XfZ8PnqNXbRA,4534
|
35
|
-
reconcile/gitlab_members.py,sha256=
|
35
|
+
reconcile/gitlab_members.py,sha256=MUIgYDLeJx2-_vMypyq2Pa17cpKdXATYhtVACS2ghpQ,8297
|
36
36
|
reconcile/gitlab_mr_sqs_consumer.py,sha256=O46mdziPgGOndbU-0_UJKJVUaiEoVzJPEgKm4_UvYoI,2571
|
37
37
|
reconcile/gitlab_owners.py,sha256=sn9njaKOtqcvnhi2qtm-faAfAR4zNqflbSuusA9RUuI,13456
|
38
38
|
reconcile/gitlab_permissions.py,sha256=6ZBPnQci4-1LVJBvaUS-c0QwIJjITNKVflCeH-Y5Lbk,7507
|
@@ -112,7 +112,7 @@ reconcile/terraform_aws_route53.py,sha256=dQzzT46YhwRA902_H6pi-f7WlX4EaH187wXSdm
|
|
112
112
|
reconcile/terraform_cloudflare_dns.py,sha256=-aLEe2QnH5cJPu7HWqs-R9NmQ1NlFbcVUm0v7alVL3I,13431
|
113
113
|
reconcile/terraform_cloudflare_resources.py,sha256=pq8Ieo5NmB-dYQ9X2F0s6iEoINMzhiqGw2yQK4ovok4,14980
|
114
114
|
reconcile/terraform_cloudflare_users.py,sha256=iyTG5sj20Jg4J4qWJ144KVptfIHGOSfH8wQKxu0imq0,13942
|
115
|
-
reconcile/terraform_repo.py,sha256=
|
115
|
+
reconcile/terraform_repo.py,sha256=jT8zD-V1_5D6yB2gLYP1nEC9wHoHktDQYnJqpo2EC9M,15807
|
116
116
|
reconcile/terraform_resources.py,sha256=iufjMJs_aSEvmh7Cg11beCxKmV8nrOLOpEtiTryPNx0,19470
|
117
117
|
reconcile/terraform_tgw_attachments.py,sha256=09svJG9pAiwWp4aY0xRoQRV90T4ZNwHG3r8flI-ZS_s,18810
|
118
118
|
reconcile/terraform_users.py,sha256=HqSm3ev3b8dZ9J6F_phDZB-FQsnlsdeKp9RPoY1cU94,10188
|
@@ -520,7 +520,7 @@ reconcile/test/test_github_org.py,sha256=aGWeMhkz1fjogvaAQGjemSKR201L5gEZZOe0iMQ
|
|
520
520
|
reconcile/test/test_github_repo_invites.py,sha256=WrPn5ROAoJYK0ihzlZcFR0V9Pu2HcMs13I6nazf7hq4,3307
|
521
521
|
reconcile/test/test_gitlab_housekeeping.py,sha256=ScD9Tgf9OOgGfAFfTy6Kn2222l2wH_A3gMRKdpvoWe0,10053
|
522
522
|
reconcile/test/test_gitlab_labeler.py,sha256=PmYXiU2g0_O5OTdMGPzdeqBAfat92U9bhjjfeyiGSmQ,4336
|
523
|
-
reconcile/test/test_gitlab_members.py,sha256=
|
523
|
+
reconcile/test/test_gitlab_members.py,sha256=bupl1dsLor-913LGYCuRtJMKqDsXo_d_2nbtDe6B1Us,7016
|
524
524
|
reconcile/test/test_gitlab_permissions.py,sha256=aMf5SUeVp-aQ1bWGQPQLYa85auzRlyfZVTWqyybJ6mo,5850
|
525
525
|
reconcile/test/test_instrumented_wrappers.py,sha256=CZzhnQH0c4i7-Rxjg7-0dfFMvVPegLHL46z5NHOOCwo,608
|
526
526
|
reconcile/test/test_integrations_manager.py,sha256=xpyQAVz57wAbovrcQzAeuyq8VzdItUyW2d2kp1WW_5c,38184
|
@@ -566,7 +566,7 @@ reconcile/test/test_terraform_aws_route53.py,sha256=xHggb8K1P76OyCfFcogbkmyKle-N
|
|
566
566
|
reconcile/test/test_terraform_cloudflare_dns.py,sha256=aQTXX8Vr4h9aWvJZTnpZEhMGYoBpT2d45ZxU_ECIQ6o,3425
|
567
567
|
reconcile/test/test_terraform_cloudflare_resources.py,sha256=1mdSZS-38mtTSg7teJgDCJU1b_yKbwnrr3N5m8kwV4k,13595
|
568
568
|
reconcile/test/test_terraform_cloudflare_users.py,sha256=2VGBtMUhckLPtUnQlHIzpGsCnyVJZPNLFf-ABELkxbQ,27456
|
569
|
-
reconcile/test/test_terraform_repo.py,sha256=
|
569
|
+
reconcile/test/test_terraform_repo.py,sha256=r6bwryHtq6-VuUh3fuMsxlsjbQAgrcQUHOEFou_hilQ,12260
|
570
570
|
reconcile/test/test_terraform_resources.py,sha256=8C97yXIEihaQ3DZrtjxLNt4y4G12IOhD01ydm7JjliY,15359
|
571
571
|
reconcile/test/test_terraform_tgw_attachments.py,sha256=pJFmQzUwAn-wKpF6oSkImpdF7WXvcky8iaJiXbjGGgU,41104
|
572
572
|
reconcile/test/test_terraform_users.py,sha256=XOAfGvITCJPI1LTlISmHbA4ONMQMkxYUMTsny7pQCFw,4319
|
@@ -678,7 +678,7 @@ reconcile/utils/external_resources.py,sha256=y7Wz32cOAmCsUhQ6T-1N6lktnLikGkaHQ0S
|
|
678
678
|
reconcile/utils/filtering.py,sha256=S4PbMHuFr3ED0P2Q_ea5CAaB7FimI62B-F5YTaKrphA,402
|
679
679
|
reconcile/utils/git.py,sha256=wzVIYAeKlMGW538U1mkJWUI6h_mFRUY4lawh2AR8hw4,2345
|
680
680
|
reconcile/utils/github_api.py,sha256=R8OvqyPdnRqvP-Efnv9RvIcbBlb4M0KC4RlbnJMD0Tg,2426
|
681
|
-
reconcile/utils/gitlab_api.py,sha256=
|
681
|
+
reconcile/utils/gitlab_api.py,sha256=ar3D1gaPGm71ecQReMvbMbBzCa8TRs3Pn1ZZJP_lwSw,28453
|
682
682
|
reconcile/utils/gpg.py,sha256=EKG7_fdMv8BMlV5yUdPiqoTx-KrzmVSEAl2sLkaKwWI,1123
|
683
683
|
reconcile/utils/gql.py,sha256=C0thIm_k9MBldfqwHzyqtYZk9sIvMdm9IbbnXLGwjD8,14158
|
684
684
|
reconcile/utils/grouping.py,sha256=vr9SFHZ7bqmHYrvYcEZt-Er3-yQYfAAdq5sHLZVmXPY,456
|
@@ -880,8 +880,8 @@ tools/test/test_qontract_cli.py,sha256=iuzKbQ6ahinvjoQmQLBrG4shey0z-1rB6qCgS8T6d
|
|
880
880
|
tools/test/test_saas_promotion_state.py,sha256=dy4kkSSAQ7bC0Xp2CociETGN-2aABEfL6FU5D9Jl00Y,6056
|
881
881
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
882
882
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
883
|
-
qontract_reconcile-0.10.
|
884
|
-
qontract_reconcile-0.10.
|
885
|
-
qontract_reconcile-0.10.
|
886
|
-
qontract_reconcile-0.10.
|
887
|
-
qontract_reconcile-0.10.
|
883
|
+
qontract_reconcile-0.10.1rc1181.dist-info/METADATA,sha256=pDZwlb7DGvqkX4trR3vqj0NqCsBK-LPoCvo46Uufung,2213
|
884
|
+
qontract_reconcile-0.10.1rc1181.dist-info/WHEEL,sha256=bFJAMchF8aTQGUgMZzHJyDDMPTO3ToJ7x23SLJa1SVo,92
|
885
|
+
qontract_reconcile-0.10.1rc1181.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
|
886
|
+
qontract_reconcile-0.10.1rc1181.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
887
|
+
qontract_reconcile-0.10.1rc1181.dist-info/RECORD,,
|
reconcile/gitlab_members.py
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
-
import enum
|
2
1
|
import logging
|
3
2
|
from collections.abc import Callable
|
4
3
|
from typing import Any
|
5
4
|
|
5
|
+
from gitlab.v4.objects import (
|
6
|
+
Group,
|
7
|
+
GroupMember,
|
8
|
+
)
|
6
9
|
from pydantic import BaseModel
|
7
10
|
|
8
11
|
from reconcile import queries
|
@@ -23,6 +26,7 @@ from reconcile.gql_definitions.gitlab_members.permissions import (
|
|
23
26
|
)
|
24
27
|
from reconcile.utils import gql
|
25
28
|
from reconcile.utils.defer import defer
|
29
|
+
from reconcile.utils.differ import diff_mappings
|
26
30
|
from reconcile.utils.exceptions import AppInterfaceSettingsError
|
27
31
|
from reconcile.utils.gitlab_api import GitLabApi
|
28
32
|
from reconcile.utils.pagerduty_api import (
|
@@ -37,50 +41,51 @@ QONTRACT_INTEGRATION = "gitlab-members"
|
|
37
41
|
|
38
42
|
class GitlabUser(BaseModel):
|
39
43
|
user: str
|
40
|
-
access_level:
|
44
|
+
access_level: int
|
41
45
|
|
42
46
|
|
43
|
-
|
47
|
+
class CurrentStateSpec(BaseModel):
|
48
|
+
members: dict[str, GroupMember]
|
44
49
|
|
50
|
+
class Config:
|
51
|
+
arbitrary_types_allowed = True
|
45
52
|
|
46
|
-
class Action(enum.Enum):
|
47
|
-
add_user_to_group = enum.auto()
|
48
|
-
remove_user_from_group = enum.auto()
|
49
|
-
change_access = enum.auto()
|
50
53
|
|
54
|
+
class DesiredStateSpec(BaseModel):
|
55
|
+
members: dict[str, GitlabUser]
|
56
|
+
|
57
|
+
class Config:
|
58
|
+
arbitrary_types_allowed = True
|
51
59
|
|
52
|
-
class Diff(BaseModel):
|
53
|
-
action: Action
|
54
|
-
group: str
|
55
|
-
user: str
|
56
|
-
access_level: str
|
57
60
|
|
61
|
+
CurrentState = dict[str, CurrentStateSpec]
|
62
|
+
DesiredState = dict[str, DesiredStateSpec]
|
58
63
|
|
59
|
-
|
64
|
+
|
65
|
+
def get_current_state(
|
66
|
+
instance: GitlabInstanceV1, gl: GitLabApi, gitlab_groups_map: dict[str, Group]
|
67
|
+
) -> CurrentState:
|
60
68
|
"""Get current gitlab group members for all managed groups."""
|
61
69
|
return {
|
62
|
-
g:
|
63
|
-
|
64
|
-
|
65
|
-
|
70
|
+
g: CurrentStateSpec(
|
71
|
+
members={
|
72
|
+
u.username: u for u in gl.get_group_members(gitlab_groups_map.get(g))
|
73
|
+
},
|
74
|
+
)
|
66
75
|
for g in instance.managed_groups
|
67
76
|
}
|
68
77
|
|
69
78
|
|
70
79
|
def add_or_update_user(
|
71
|
-
|
80
|
+
desired_state_spec: DesiredStateSpec, gitlab_user: GitlabUser
|
72
81
|
) -> None:
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
if not existing_users:
|
77
|
-
group_members[group_name].append(gitlab_user)
|
82
|
+
existing_user = desired_state_spec.members.get(gitlab_user.user)
|
83
|
+
if not existing_user:
|
84
|
+
desired_state_spec.members[gitlab_user.user] = gitlab_user
|
78
85
|
else:
|
79
|
-
existing_user =
|
80
|
-
|
81
|
-
|
82
|
-
) < GitLabApi.get_access_level(gitlab_user.access_level):
|
83
|
-
existing_user.access_level = gitlab_user.access_level
|
86
|
+
existing_user.access_level = max(
|
87
|
+
existing_user.access_level, gitlab_user.access_level
|
88
|
+
)
|
84
89
|
|
85
90
|
|
86
91
|
def get_desired_state(
|
@@ -88,95 +93,41 @@ def get_desired_state(
|
|
88
93
|
pagerduty_map: PagerDutyMap,
|
89
94
|
permissions: list[PermissionGitlabGroupMembershipV1],
|
90
95
|
all_users: list[User],
|
91
|
-
) ->
|
96
|
+
) -> DesiredState:
|
92
97
|
"""Fetch all desired gitlab users from app-interface."""
|
93
|
-
desired_group_members:
|
94
|
-
|
95
|
-
for
|
96
|
-
|
97
|
-
for r in p.roles or []:
|
98
|
-
for u in (r.users or []) + (r.bots or []):
|
99
|
-
gu = GitlabUser(user=u.org_username, access_level=p.access)
|
100
|
-
add_or_update_user(desired_group_members, g, gu)
|
101
|
-
if p.pagerduty:
|
102
|
-
usernames_from_pagerduty = get_usernames_from_pagerduty(
|
103
|
-
p.pagerduty,
|
104
|
-
all_users,
|
105
|
-
g,
|
106
|
-
pagerduty_map,
|
107
|
-
get_username_method=lambda u: u.org_username,
|
108
|
-
)
|
109
|
-
for u in usernames_from_pagerduty:
|
110
|
-
gu = GitlabUser(user=u, access_level=p.access)
|
111
|
-
add_or_update_user(desired_group_members, g, gu)
|
112
|
-
|
98
|
+
desired_group_members: DesiredState = {
|
99
|
+
g: build_desired_state_spec(g, permissions, pagerduty_map, all_users)
|
100
|
+
for g in instance.managed_groups
|
101
|
+
}
|
113
102
|
return desired_group_members
|
114
103
|
|
115
104
|
|
116
|
-
def
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
)
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
found = True
|
138
|
-
break
|
139
|
-
if not found:
|
140
|
-
result.append(
|
141
|
-
Diff(
|
142
|
-
action=action,
|
143
|
-
group=f_group,
|
144
|
-
user=f_user.user,
|
145
|
-
access_level=f_user.access_level,
|
146
|
-
)
|
105
|
+
def build_desired_state_spec(
|
106
|
+
group_name: str,
|
107
|
+
permissions: list[PermissionGitlabGroupMembershipV1],
|
108
|
+
pagerduty_map: PagerDutyMap,
|
109
|
+
all_users: list[User],
|
110
|
+
) -> DesiredStateSpec:
|
111
|
+
desired_state_spec = DesiredStateSpec(members={})
|
112
|
+
for p in permissions:
|
113
|
+
if p.group == group_name:
|
114
|
+
p_access_level = GitLabApi.get_access_level(p.access)
|
115
|
+
for r in p.roles or []:
|
116
|
+
for u in (r.users or []) + (r.bots or []):
|
117
|
+
gu = GitlabUser(user=u.org_username, access_level=p_access_level)
|
118
|
+
add_or_update_user(desired_state_spec, gu)
|
119
|
+
if p.pagerduty:
|
120
|
+
usernames_from_pagerduty = get_usernames_from_pagerduty(
|
121
|
+
p.pagerduty,
|
122
|
+
all_users,
|
123
|
+
group_name,
|
124
|
+
pagerduty_map,
|
125
|
+
get_username_method=lambda u: u.org_username,
|
147
126
|
)
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
"""Return diff objects for item where access level is different."""
|
153
|
-
result = []
|
154
|
-
for d_group, d_users in desired_state.items():
|
155
|
-
c_group = current_state[d_group]
|
156
|
-
for d_user in d_users:
|
157
|
-
for c_user in c_group:
|
158
|
-
if d_user.user == c_user.user:
|
159
|
-
if d_user.access_level != c_user.access_level:
|
160
|
-
result.append(
|
161
|
-
Diff(
|
162
|
-
action=Action.change_access,
|
163
|
-
group=d_group,
|
164
|
-
user=c_user.user,
|
165
|
-
access_level=d_user.access_level,
|
166
|
-
)
|
167
|
-
)
|
168
|
-
break
|
169
|
-
return result
|
170
|
-
|
171
|
-
|
172
|
-
def act(diff: Diff, gl: GitLabApi) -> None:
|
173
|
-
"""Apply a diff object."""
|
174
|
-
if diff.action == Action.remove_user_from_group:
|
175
|
-
gl.remove_group_member(diff.group, diff.user)
|
176
|
-
if diff.action == Action.add_user_to_group:
|
177
|
-
gl.add_group_member(diff.group, diff.user, diff.access_level)
|
178
|
-
if diff.action == Action.change_access:
|
179
|
-
gl.change_access(diff.group, diff.user, diff.access_level)
|
127
|
+
for u in usernames_from_pagerduty:
|
128
|
+
gu = GitlabUser(user=u, access_level=p_access_level)
|
129
|
+
add_or_update_user(desired_state_spec, gu)
|
130
|
+
return desired_state_spec
|
180
131
|
|
181
132
|
|
182
133
|
def get_permissions(query_func: Callable) -> list[PermissionGitlabGroupMembershipV1]:
|
@@ -197,6 +148,11 @@ def get_gitlab_instance(query_func: Callable) -> GitlabInstanceV1:
|
|
197
148
|
raise AppInterfaceSettingsError("No gitlab instance found!")
|
198
149
|
|
199
150
|
|
151
|
+
def get_managed_groups_map(group_names: list[str], gl: GitLabApi) -> dict[str, Group]:
|
152
|
+
gitlab_groups = {group_name: gl.get_group(group_name) for group_name in group_names}
|
153
|
+
return gitlab_groups
|
154
|
+
|
155
|
+
|
200
156
|
@defer
|
201
157
|
def run(
|
202
158
|
dry_run: bool,
|
@@ -228,17 +184,58 @@ def run(
|
|
228
184
|
pagerduty_map = get_pagerduty_map(
|
229
185
|
secret_reader, pagerduty_instances=pagerduty_instances
|
230
186
|
)
|
231
|
-
|
232
|
-
|
233
|
-
current_state = get_current_state(instance, gl)
|
187
|
+
managed_groups_map = get_managed_groups_map(instance.managed_groups, gl)
|
188
|
+
current_state = get_current_state(instance, gl, managed_groups_map)
|
234
189
|
desired_state = get_desired_state(instance, pagerduty_map, permissions, all_users)
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
190
|
+
for group_name in instance.managed_groups:
|
191
|
+
reconcile_gitlab_members(
|
192
|
+
current_state_spec=current_state.get(group_name),
|
193
|
+
desired_state_spec=desired_state.get(group_name),
|
194
|
+
group=managed_groups_map.get(group_name),
|
195
|
+
gl=gl,
|
196
|
+
dry_run=dry_run,
|
197
|
+
)
|
198
|
+
|
199
|
+
|
200
|
+
def reconcile_gitlab_members(
|
201
|
+
current_state_spec: CurrentStateSpec | None,
|
202
|
+
desired_state_spec: DesiredStateSpec | None,
|
203
|
+
group: Group | None,
|
204
|
+
gl: GitLabApi,
|
205
|
+
dry_run: bool,
|
206
|
+
) -> None:
|
207
|
+
if current_state_spec and desired_state_spec and group:
|
208
|
+
diff_data = diff_mappings(
|
209
|
+
current=current_state_spec.members,
|
210
|
+
desired=desired_state_spec.members,
|
211
|
+
equal=lambda current, desired: current.access_level == desired.access_level,
|
212
|
+
)
|
213
|
+
for key, gitlab_user in diff_data.add.items():
|
214
|
+
logging.info([
|
215
|
+
key,
|
216
|
+
"add_user_to_group",
|
217
|
+
group.name,
|
218
|
+
gl.get_access_level_string(gitlab_user.access_level),
|
219
|
+
])
|
220
|
+
if not dry_run:
|
221
|
+
gl.add_group_member(group, gitlab_user)
|
222
|
+
for key, group_member in diff_data.delete.items():
|
223
|
+
logging.info([
|
224
|
+
key,
|
225
|
+
"remove_user_from_group",
|
226
|
+
group.name,
|
227
|
+
])
|
228
|
+
if not dry_run:
|
229
|
+
gl.remove_group_member(group, group_member.id)
|
230
|
+
for key, diff_pair in diff_data.change.items():
|
231
|
+
logging.info([
|
232
|
+
key,
|
233
|
+
"change_access",
|
234
|
+
group.name,
|
235
|
+
gl.get_access_level_string(diff_pair.desired.access_level),
|
236
|
+
])
|
237
|
+
if not dry_run:
|
238
|
+
gl.change_access(diff_pair.current, diff_pair.desired.access_level)
|
242
239
|
|
243
240
|
|
244
241
|
def early_exit_desired_state(*args: Any, **kwargs: Any) -> dict[str, Any]:
|
reconcile/terraform_repo.py
CHANGED
@@ -100,7 +100,6 @@ class TerraformRepoIntegration(
|
|
100
100
|
desired_state=desired,
|
101
101
|
dry_run=dry_run,
|
102
102
|
state=state,
|
103
|
-
recreate_state=False,
|
104
103
|
)
|
105
104
|
|
106
105
|
if repo_diff_result:
|
@@ -110,18 +109,19 @@ class TerraformRepoIntegration(
|
|
110
109
|
# the existing state stored in S3. This is due to a behavior in Pydantic V1 that has since been addressed in V2
|
111
110
|
# https://docs.pydantic.dev/latest/blog/pydantic-v2/#required-vs-nullable-cleanup
|
112
111
|
# so in this case, tf-repo will just recreate all of those state files in S3 and not actually do a plan or apply
|
113
|
-
logging.
|
114
|
-
|
115
|
-
|
112
|
+
logging.warning(
|
113
|
+
"Unable to parse existing Terraform-Repo state from S3. Will re-create internal state upon apply, more info here: https://gitlab.cee.redhat.com/service/app-interface/-/blob/master/docs/terraform-repo/sop/statefile-load-errors.md",
|
114
|
+
exc_info=err,
|
116
115
|
)
|
117
116
|
repo_diff_result = self.calculate_diff(
|
118
117
|
existing_state=[],
|
119
118
|
desired_state=desired,
|
120
119
|
dry_run=dry_run,
|
121
120
|
state=state,
|
122
|
-
recreate_state=True,
|
123
121
|
)
|
124
122
|
|
123
|
+
# we don't call print_output here meaning that the executor will not plan/apply
|
124
|
+
|
125
125
|
def print_output(self, diff: list[TerraformRepoV1], dry_run: bool) -> OutputFile:
|
126
126
|
"""Parses and prints the output of a Terraform Repo diff for the executor
|
127
127
|
|
@@ -302,7 +302,6 @@ class TerraformRepoIntegration(
|
|
302
302
|
desired_state: list[TerraformRepoV1],
|
303
303
|
dry_run: bool,
|
304
304
|
state: State | None,
|
305
|
-
recreate_state: bool,
|
306
305
|
) -> list[TerraformRepoV1] | None:
|
307
306
|
"""Calculated the difference between existing and desired state
|
308
307
|
to determine what actions the executor will need to take
|
@@ -315,8 +314,6 @@ class TerraformRepoIntegration(
|
|
315
314
|
:type dry_run: bool
|
316
315
|
:param state: AWS S3 state
|
317
316
|
:type state: Optional[State]
|
318
|
-
:param recreate_state: whether we are recreating our own state
|
319
|
-
:type recreate_state: bool
|
320
317
|
:raises ParameterError: if there is an invalid operation performed like trying to delete
|
321
318
|
a representation in A-I before setting the delete flag
|
322
319
|
:return: the terraform repo to act on
|
@@ -326,13 +323,6 @@ class TerraformRepoIntegration(
|
|
326
323
|
|
327
324
|
merged = self.merge_results(diff)
|
328
325
|
|
329
|
-
# validate that only one repo is being modified in each MR
|
330
|
-
# this lets us fail early and avoid multiple GL requests we don't need to make
|
331
|
-
if dry_run and len(merged) > 1 and not recreate_state:
|
332
|
-
raise Exception(
|
333
|
-
"Only one repository can be modified per merge request, please split your change out into multiple MRs. Hint: try rebasing your merge request"
|
334
|
-
)
|
335
|
-
|
336
326
|
# added repos: do standard validation that SHA is valid
|
337
327
|
if self.params.validate_git:
|
338
328
|
for add_repo in diff.add.values():
|
@@ -1,15 +1,20 @@
|
|
1
|
-
import copy
|
2
1
|
from typing import Any
|
2
|
+
from unittest.mock import create_autospec
|
3
3
|
|
4
4
|
import pytest
|
5
|
+
from gitlab.v4.objects import (
|
6
|
+
Group,
|
7
|
+
GroupMember,
|
8
|
+
)
|
5
9
|
from pytest_mock import MockerFixture
|
6
10
|
|
7
11
|
from reconcile import gitlab_members
|
8
12
|
from reconcile.gitlab_members import (
|
9
|
-
|
10
|
-
|
13
|
+
CurrentState,
|
14
|
+
CurrentStateSpec,
|
15
|
+
DesiredState,
|
16
|
+
DesiredStateSpec,
|
11
17
|
GitlabUser,
|
12
|
-
State,
|
13
18
|
add_or_update_user,
|
14
19
|
get_permissions,
|
15
20
|
)
|
@@ -37,16 +42,41 @@ def instance(vault_secret: VaultSecret) -> GitlabInstanceV1:
|
|
37
42
|
|
38
43
|
|
39
44
|
@pytest.fixture()
|
40
|
-
def
|
45
|
+
def gitlab_groups_map() -> dict[str, Group]:
|
46
|
+
group1 = create_autospec(Group, name="group1", id="123")
|
47
|
+
group1.name = "group1"
|
48
|
+
group2 = create_autospec(Group, name="group2", id="124")
|
49
|
+
group2.name = "group2"
|
41
50
|
return {
|
42
|
-
"group1":
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
51
|
+
"group1": group1,
|
52
|
+
"group2": group2,
|
53
|
+
}
|
54
|
+
|
55
|
+
|
56
|
+
@pytest.fixture()
|
57
|
+
def all_users() -> list[GroupMember]:
|
58
|
+
user1 = create_autospec(GroupMember, username="user1", id="123", access_level=30)
|
59
|
+
user2 = create_autospec(GroupMember, username="user2", id="124", access_level=40)
|
60
|
+
user3 = create_autospec(GroupMember, username="user3", id="125", access_level=30)
|
61
|
+
user4 = create_autospec(GroupMember, username="user4", id="126", access_level=40)
|
62
|
+
return [user1, user2, user3, user4]
|
63
|
+
|
64
|
+
|
65
|
+
@pytest.fixture()
|
66
|
+
def current_state(all_users: list[GroupMember]) -> CurrentState:
|
67
|
+
return {
|
68
|
+
"group1": CurrentStateSpec(
|
69
|
+
members={
|
70
|
+
"user1": all_users[0],
|
71
|
+
"user2": all_users[1],
|
72
|
+
},
|
73
|
+
),
|
74
|
+
"group2": CurrentStateSpec(
|
75
|
+
members={
|
76
|
+
"user3": all_users[2],
|
77
|
+
"user4": all_users[3],
|
78
|
+
},
|
79
|
+
),
|
50
80
|
}
|
51
81
|
|
52
82
|
|
@@ -72,20 +102,27 @@ def user() -> User:
|
|
72
102
|
|
73
103
|
|
74
104
|
def test_gitlab_members_get_current_state(
|
75
|
-
mocker: MockerFixture,
|
105
|
+
mocker: MockerFixture,
|
106
|
+
instance: GitlabInstanceV1,
|
107
|
+
current_state: CurrentState,
|
108
|
+
gitlab_groups_map: dict[str, Group],
|
109
|
+
all_users: list[GroupMember],
|
76
110
|
) -> None:
|
77
111
|
gl_mock = mocker.create_autospec(GitLabApi)
|
78
112
|
gl_mock.get_group_members.side_effect = [
|
79
113
|
[
|
80
|
-
|
81
|
-
|
114
|
+
all_users[0],
|
115
|
+
all_users[1],
|
82
116
|
],
|
83
117
|
[
|
84
|
-
|
85
|
-
|
118
|
+
all_users[2],
|
119
|
+
all_users[3],
|
86
120
|
],
|
87
121
|
]
|
88
|
-
assert
|
122
|
+
assert (
|
123
|
+
gitlab_members.get_current_state(instance, gl_mock, gitlab_groups_map)
|
124
|
+
== current_state
|
125
|
+
)
|
89
126
|
|
90
127
|
|
91
128
|
def test_gitlab_members_get_desired_state(
|
@@ -103,210 +140,80 @@ def test_gitlab_members_get_desired_state(
|
|
103
140
|
assert gitlab_members.get_desired_state(
|
104
141
|
instance, mock_pagerduty_map, permissions, [user]
|
105
142
|
) == {
|
106
|
-
"group1":
|
107
|
-
|
108
|
-
GitlabUser(user="devtools-bot", access_level="owner"),
|
109
|
-
GitlabUser(user="user1", access_level="owner"),
|
110
|
-
GitlabUser(user="user2", access_level="owner"),
|
111
|
-
GitlabUser(user="user3", access_level="owner"),
|
112
|
-
GitlabUser(user="user4", access_level="owner"),
|
113
|
-
GitlabUser(user="another-bot", access_level="owner"),
|
114
|
-
],
|
115
|
-
}
|
116
|
-
|
117
|
-
|
118
|
-
def test_gitlab_members_calculate_diff_no_changes(state: State) -> None:
|
119
|
-
# pylint: disable-next=use-implicit-booleaness-not-comparison # for better readability
|
120
|
-
assert gitlab_members.calculate_diff(state, state) == []
|
121
|
-
|
122
|
-
|
123
|
-
def test_gitlab_members_subtract_states_no_changes_add(state: State) -> None:
|
124
|
-
# pylint: disable-next=use-implicit-booleaness-not-comparison # for better readability
|
125
|
-
assert gitlab_members.subtract_states(state, state, Action.add_user_to_group) == []
|
126
|
-
|
127
|
-
|
128
|
-
def test_gitlab_members_subtract_states_no_changes_remove(state: State) -> None:
|
129
|
-
# pylint: disable=use-implicit-booleaness-not-comparison # for better readability
|
130
|
-
assert (
|
131
|
-
gitlab_members.subtract_states(state, state, Action.remove_user_from_group)
|
132
|
-
== []
|
133
|
-
)
|
134
|
-
|
135
|
-
|
136
|
-
def test_gitlab_members_subtract_states_add(state: State) -> None:
|
137
|
-
current_state = copy.deepcopy(state)
|
138
|
-
# enforce add users to groups
|
139
|
-
current_state["group2"] = [GitlabUser(access_level="maintainer", user="otherone")]
|
140
|
-
del current_state["group1"][1]
|
141
|
-
|
142
|
-
desired_state = state
|
143
|
-
assert gitlab_members.subtract_states(
|
144
|
-
desired_state, current_state, Action.add_user_to_group
|
145
|
-
) == [
|
146
|
-
Diff(
|
147
|
-
action=Action.add_user_to_group,
|
148
|
-
group="group1",
|
149
|
-
user="user2",
|
150
|
-
access_level="maintainer",
|
151
|
-
),
|
152
|
-
Diff(
|
153
|
-
action=Action.add_user_to_group,
|
154
|
-
group="group2",
|
155
|
-
user="user3",
|
156
|
-
access_level="developer",
|
157
|
-
),
|
158
|
-
Diff(
|
159
|
-
action=Action.add_user_to_group,
|
160
|
-
group="group2",
|
161
|
-
user="user4",
|
162
|
-
access_level="maintainer",
|
163
|
-
),
|
164
|
-
]
|
165
|
-
|
166
|
-
|
167
|
-
def test_gitlab_members_subtract_states_remove(state: State) -> None:
|
168
|
-
current_state = copy.deepcopy(state)
|
169
|
-
# enforce remove user from group
|
170
|
-
current_state["group2"] = [GitlabUser(access_level="maintainer", user="otherone")]
|
171
|
-
|
172
|
-
desired_state = state
|
173
|
-
assert gitlab_members.subtract_states(
|
174
|
-
current_state, desired_state, Action.remove_user_from_group
|
175
|
-
) == [
|
176
|
-
Diff(
|
177
|
-
action=Action.remove_user_from_group,
|
178
|
-
group="group2",
|
179
|
-
user="otherone",
|
180
|
-
access_level="maintainer",
|
181
|
-
)
|
182
|
-
]
|
183
|
-
|
184
|
-
|
185
|
-
def test_gitlab_members_check_access_no_changes(state: State) -> None:
|
186
|
-
# pylint: disable-next=use-implicit-booleaness-not-comparison # for better readability
|
187
|
-
assert gitlab_members.check_access(state, state) == []
|
188
|
-
|
189
|
-
|
190
|
-
def test_gitlab_members_check_access(state: State) -> None:
|
191
|
-
current_state = copy.deepcopy(state)
|
192
|
-
# enforce access change
|
193
|
-
current_state["group1"][0].access_level = "owner"
|
194
|
-
desired_state = state
|
195
|
-
assert gitlab_members.check_access(current_state, desired_state) == [
|
196
|
-
Diff(
|
197
|
-
action=Action.change_access,
|
198
|
-
group="group1",
|
199
|
-
user="user1",
|
200
|
-
access_level="developer",
|
201
|
-
),
|
202
|
-
]
|
203
|
-
|
204
|
-
|
205
|
-
def test_gitlab_members_calculate_diff_changes(state: State) -> None:
|
206
|
-
current_state = copy.deepcopy(state)
|
207
|
-
# enforce remove user from group
|
208
|
-
current_state["group2"] = [GitlabUser(access_level="maintainer", user="otherone")]
|
209
|
-
# enforce add user to group
|
210
|
-
del current_state["group1"][1]
|
211
|
-
# enforce access change
|
212
|
-
current_state["group1"][0].access_level = "owner"
|
213
|
-
desired_state = state
|
214
|
-
assert gitlab_members.calculate_diff(current_state, desired_state) == [
|
215
|
-
Diff(
|
216
|
-
action=Action.add_user_to_group,
|
217
|
-
group="group1",
|
218
|
-
user="user2",
|
219
|
-
access_level="maintainer",
|
220
|
-
),
|
221
|
-
Diff(
|
222
|
-
action=Action.add_user_to_group,
|
223
|
-
group="group2",
|
224
|
-
user="user3",
|
225
|
-
access_level="developer",
|
226
|
-
),
|
227
|
-
Diff(
|
228
|
-
action=Action.add_user_to_group,
|
229
|
-
group="group2",
|
230
|
-
user="user4",
|
231
|
-
access_level="maintainer",
|
232
|
-
),
|
233
|
-
Diff(
|
234
|
-
action=Action.remove_user_from_group,
|
235
|
-
group="group2",
|
236
|
-
user="otherone",
|
237
|
-
access_level="maintainer",
|
143
|
+
"group1": DesiredStateSpec(
|
144
|
+
members={"devtools-bot": GitlabUser(user="devtools-bot", access_level=50)},
|
238
145
|
),
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
146
|
+
"group2": DesiredStateSpec(
|
147
|
+
members={
|
148
|
+
"devtools-bot": GitlabUser(user="devtools-bot", access_level=50),
|
149
|
+
"user1": GitlabUser(user="user1", access_level=50),
|
150
|
+
"user2": GitlabUser(user="user2", access_level=50),
|
151
|
+
"user3": GitlabUser(user="user3", access_level=50),
|
152
|
+
"user4": GitlabUser(user="user4", access_level=50),
|
153
|
+
"another-bot": GitlabUser(user="another-bot", access_level=50),
|
154
|
+
},
|
244
155
|
),
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
def test_gitlab_members_act_add(mocker: MockerFixture) -> None:
|
249
|
-
gl_mock = mocker.create_autospec(GitLabApi)
|
250
|
-
diff = Diff(
|
251
|
-
action=Action.add_user_to_group,
|
252
|
-
group="group2",
|
253
|
-
user="user4",
|
254
|
-
access_level="maintainer",
|
255
|
-
)
|
256
|
-
gitlab_members.act(diff, gl_mock)
|
257
|
-
gl_mock.add_group_member.assert_called_once()
|
258
|
-
gl_mock.remove_group_member.assert_not_called()
|
259
|
-
gl_mock.change_access.assert_not_called()
|
260
|
-
|
261
|
-
|
262
|
-
def test_gitlab_members_act_remove(mocker: MockerFixture) -> None:
|
263
|
-
gl_mock = mocker.create_autospec(GitLabApi)
|
264
|
-
diff = Diff(
|
265
|
-
action=Action.remove_user_from_group,
|
266
|
-
group="group2",
|
267
|
-
user="otherone",
|
268
|
-
access_level="maintainer",
|
269
|
-
)
|
270
|
-
gitlab_members.act(diff, gl_mock)
|
271
|
-
gl_mock.add_group_member.assert_not_called()
|
272
|
-
gl_mock.remove_group_member.assert_called_once()
|
273
|
-
gl_mock.change_access.assert_not_called()
|
156
|
+
}
|
274
157
|
|
275
158
|
|
276
|
-
def
|
159
|
+
def test_gitlab_members_reconcile_gitlab_members(
|
160
|
+
gitlab_groups_map: dict[str, Group],
|
161
|
+
mocker: MockerFixture,
|
162
|
+
all_users: list[GroupMember],
|
163
|
+
) -> None:
|
277
164
|
gl_mock = mocker.create_autospec(GitLabApi)
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
165
|
+
current_state: CurrentState = {
|
166
|
+
"group1": CurrentStateSpec(
|
167
|
+
members={
|
168
|
+
"user1": all_users[0],
|
169
|
+
"user3": all_users[2],
|
170
|
+
"user4": all_users[3],
|
171
|
+
},
|
172
|
+
)
|
173
|
+
}
|
174
|
+
new_user = GitlabUser(user="new_user", access_level=40)
|
175
|
+
desired_state: DesiredState = {
|
176
|
+
"group1": DesiredStateSpec(
|
177
|
+
members={
|
178
|
+
"user1": GitlabUser(user="user1", access_level=30),
|
179
|
+
"new_user": new_user,
|
180
|
+
"user3": GitlabUser(user="user3", access_level=50),
|
181
|
+
}
|
182
|
+
)
|
183
|
+
}
|
184
|
+
group = gitlab_groups_map.get("group1")
|
185
|
+
gitlab_members.reconcile_gitlab_members(
|
186
|
+
current_state_spec=current_state.get("group1"),
|
187
|
+
desired_state_spec=desired_state.get("group1"),
|
188
|
+
group=group,
|
189
|
+
dry_run=False,
|
190
|
+
gl=gl_mock,
|
283
191
|
)
|
284
|
-
|
285
|
-
gl_mock.
|
286
|
-
gl_mock.remove_group_member.
|
287
|
-
gl_mock.change_access.assert_called_once()
|
192
|
+
gl_mock.add_group_member.assert_called_once_with(group, new_user)
|
193
|
+
gl_mock.change_access.assert_called_once_with(all_users[2], 50)
|
194
|
+
gl_mock.remove_group_member.assert_called_once_with(group, all_users[3].id)
|
288
195
|
|
289
196
|
|
290
197
|
def test_add_or_update_user_add():
|
291
|
-
|
292
|
-
gu = GitlabUser(user="u", access_level="
|
293
|
-
add_or_update_user(
|
294
|
-
assert
|
198
|
+
desired_state_spec: DesiredStateSpec = DesiredStateSpec(members={})
|
199
|
+
gu = GitlabUser(user="u", access_level=50, id="1234")
|
200
|
+
add_or_update_user(desired_state_spec, gu)
|
201
|
+
assert desired_state_spec == DesiredStateSpec(members={"u": gu})
|
295
202
|
|
296
203
|
|
297
204
|
def test_add_or_update_user_update_higher():
|
298
|
-
|
299
|
-
gu1 = GitlabUser(user="u", access_level=
|
300
|
-
gu2 = GitlabUser(user="u", access_level=
|
301
|
-
add_or_update_user(
|
302
|
-
add_or_update_user(
|
303
|
-
assert
|
205
|
+
desired_state_spec: DesiredStateSpec = DesiredStateSpec(members={})
|
206
|
+
gu1 = GitlabUser(user="u", access_level=40)
|
207
|
+
gu2 = GitlabUser(user="u", access_level=50)
|
208
|
+
add_or_update_user(desired_state_spec, gu1)
|
209
|
+
add_or_update_user(desired_state_spec, gu2)
|
210
|
+
assert desired_state_spec == DesiredStateSpec(members={"u": gu2})
|
304
211
|
|
305
212
|
|
306
213
|
def test_add_or_update_user_update_lower():
|
307
|
-
|
308
|
-
gu1 = GitlabUser(user="u", access_level=
|
309
|
-
gu2 = GitlabUser(user="u", access_level=
|
310
|
-
add_or_update_user(
|
311
|
-
add_or_update_user(
|
312
|
-
assert
|
214
|
+
desired_state_spec: DesiredStateSpec = DesiredStateSpec(members={})
|
215
|
+
gu1 = GitlabUser(user="u", access_level=50)
|
216
|
+
gu2 = GitlabUser(user="u", access_level=40)
|
217
|
+
add_or_update_user(desired_state_spec, gu1)
|
218
|
+
add_or_update_user(desired_state_spec, gu2)
|
219
|
+
assert desired_state_spec == DesiredStateSpec(members={"u": gu1})
|
@@ -193,7 +193,6 @@ def test_addition_to_existing_repo(existing_repo, new_repo, int_params, state_mo
|
|
193
193
|
desired_state=desired,
|
194
194
|
dry_run=False,
|
195
195
|
state=state_mock,
|
196
|
-
recreate_state=False,
|
197
196
|
)
|
198
197
|
|
199
198
|
assert diff == [new_repo]
|
@@ -215,7 +214,6 @@ def test_updating_repo_ref(existing_repo, int_params, state_mock):
|
|
215
214
|
desired_state=[updated_repo],
|
216
215
|
dry_run=False,
|
217
216
|
state=state_mock,
|
218
|
-
recreate_state=False,
|
219
217
|
)
|
220
218
|
|
221
219
|
assert diff == [updated_repo]
|
@@ -236,7 +234,6 @@ def test_force_rerun(existing_repo, int_params, state_mock):
|
|
236
234
|
desired_state=[updated_repo],
|
237
235
|
dry_run=False,
|
238
236
|
state=state_mock,
|
239
|
-
recreate_state=False,
|
240
237
|
)
|
241
238
|
|
242
239
|
assert diff == [updated_repo]
|
@@ -263,7 +260,6 @@ def test_fail_on_update_invalid_repo_params(existing_repo, int_params):
|
|
263
260
|
desired_state=[updated_repo],
|
264
261
|
dry_run=True,
|
265
262
|
state=None,
|
266
|
-
recreate_state=False,
|
267
263
|
)
|
268
264
|
|
269
265
|
|
@@ -279,7 +275,6 @@ def test_delete_repo(existing_repo, int_params, state_mock):
|
|
279
275
|
desired_state=[updated_repo],
|
280
276
|
dry_run=False,
|
281
277
|
state=state_mock,
|
282
|
-
recreate_state=False,
|
283
278
|
)
|
284
279
|
|
285
280
|
assert diff == [updated_repo]
|
@@ -298,7 +293,6 @@ def test_delete_repo_without_flag(existing_repo, int_params):
|
|
298
293
|
desired_state=[],
|
299
294
|
dry_run=True,
|
300
295
|
state=None,
|
301
|
-
recreate_state=False,
|
302
296
|
)
|
303
297
|
|
304
298
|
|
@@ -361,7 +355,6 @@ def test_update_repo_state(int_params, existing_repo, state_mock):
|
|
361
355
|
desired_state=desired_state,
|
362
356
|
dry_run=False,
|
363
357
|
state=state_mock,
|
364
|
-
recreate_state=False,
|
365
358
|
)
|
366
359
|
|
367
360
|
state_mock.add.assert_called_once_with(
|
@@ -384,7 +377,6 @@ def test_output_correct_statefile(
|
|
384
377
|
desired_state=desired_state,
|
385
378
|
dry_run=True,
|
386
379
|
state=state_mock,
|
387
|
-
recreate_state=False,
|
388
380
|
)
|
389
381
|
|
390
382
|
assert diff
|
@@ -406,7 +398,6 @@ def test_output_correct_no_statefile(
|
|
406
398
|
desired_state=desired_state,
|
407
399
|
dry_run=True,
|
408
400
|
state=state_mock,
|
409
|
-
recreate_state=False,
|
410
401
|
)
|
411
402
|
|
412
403
|
assert diff
|
@@ -415,21 +406,6 @@ def test_output_correct_no_statefile(
|
|
415
406
|
assert new_repo_output == current_output
|
416
407
|
|
417
408
|
|
418
|
-
def test_fail_on_multiple_repos_dry_run(int_params, existing_repo, new_repo):
|
419
|
-
integration = TerraformRepoIntegration(params=int_params)
|
420
|
-
|
421
|
-
desired_state = [existing_repo, new_repo]
|
422
|
-
|
423
|
-
with pytest.raises(Exception):
|
424
|
-
integration.calculate_diff(
|
425
|
-
existing_state=[],
|
426
|
-
desired_state=desired_state,
|
427
|
-
dry_run=True,
|
428
|
-
state=None,
|
429
|
-
recreate_state=False,
|
430
|
-
)
|
431
|
-
|
432
|
-
|
433
409
|
def test_succeed_on_multiple_repos_non_dry_run(int_params, existing_repo, new_repo):
|
434
410
|
integration = TerraformRepoIntegration(params=int_params)
|
435
411
|
|
@@ -440,7 +416,6 @@ def test_succeed_on_multiple_repos_non_dry_run(int_params, existing_repo, new_re
|
|
440
416
|
desired_state=desired_state,
|
441
417
|
dry_run=False,
|
442
418
|
state=None,
|
443
|
-
recreate_state=False,
|
444
419
|
)
|
445
420
|
|
446
421
|
assert diff
|
@@ -460,7 +435,6 @@ def test_no_op_succeeds(int_params, existing_repo):
|
|
460
435
|
desired_state=state,
|
461
436
|
dry_run=True,
|
462
437
|
state=None,
|
463
|
-
recreate_state=False,
|
464
438
|
)
|
465
439
|
|
466
440
|
assert diff is None
|
reconcile/utils/gitlab_api.py
CHANGED
@@ -29,6 +29,7 @@ from gitlab.const import (
|
|
29
29
|
from gitlab.v4.objects import (
|
30
30
|
CurrentUser,
|
31
31
|
Group,
|
32
|
+
GroupMember,
|
32
33
|
PersonalAccessToken,
|
33
34
|
Project,
|
34
35
|
ProjectIssue,
|
@@ -84,6 +85,7 @@ GROUP_BOT_NAME_REGEX = re.compile(r"group_.+_bot_.+")
|
|
84
85
|
|
85
86
|
|
86
87
|
class GLGroupMember(TypedDict):
|
88
|
+
id: str
|
87
89
|
user: str
|
88
90
|
access_level: str
|
89
91
|
|
@@ -288,17 +290,13 @@ class GitLabApi: # pylint: disable=too-many-public-methods
|
|
288
290
|
"""
|
289
291
|
return GROUP_BOT_NAME_REGEX.match(username) is not None
|
290
292
|
|
291
|
-
def get_group_members(self,
|
292
|
-
group = self.get_group_if_exists(group_name)
|
293
|
+
def get_group_members(self, group: Group | None) -> list[GroupMember]:
|
293
294
|
if group is None:
|
294
|
-
logging.error(
|
295
|
+
logging.error("no group provided")
|
295
296
|
return []
|
296
297
|
else:
|
297
298
|
return [
|
298
|
-
|
299
|
-
"user": m.username,
|
300
|
-
"access_level": self.get_access_level_string(m.access_level),
|
301
|
-
}
|
299
|
+
m
|
302
300
|
for m in self.get_items(group.members.list)
|
303
301
|
if not self._is_bot_username(m.username)
|
304
302
|
]
|
@@ -315,40 +313,27 @@ class GitLabApi: # pylint: disable=too-many-public-methods
|
|
315
313
|
member.access_level = access_level
|
316
314
|
member.save()
|
317
315
|
|
318
|
-
def add_group_member(self,
|
319
|
-
|
320
|
-
if not
|
321
|
-
logging.error(group_name + " group not found")
|
322
|
-
else:
|
323
|
-
user = self.get_user(username)
|
324
|
-
access_level = self.get_access_level(access)
|
325
|
-
if user is not None:
|
326
|
-
gitlab_request.labels(integration=INTEGRATION_NAME).inc()
|
327
|
-
try:
|
328
|
-
group.members.create({
|
329
|
-
"user_id": user.id,
|
330
|
-
"access_level": access_level,
|
331
|
-
})
|
332
|
-
except gitlab.exceptions.GitlabCreateError:
|
333
|
-
gitlab_request.labels(integration=INTEGRATION_NAME).inc()
|
334
|
-
member = group.members.get(user.id)
|
335
|
-
member.access_level = access_level
|
336
|
-
|
337
|
-
def remove_group_member(self, group_name, username):
|
338
|
-
gitlab_request.labels(integration=INTEGRATION_NAME).inc()
|
339
|
-
group = self.gl.groups.get(group_name)
|
340
|
-
user = self.get_user(username)
|
341
|
-
if user is not None:
|
316
|
+
def add_group_member(self, group, user):
|
317
|
+
gitlab_user = self.get_user(user.user)
|
318
|
+
if gitlab_user is not None:
|
342
319
|
gitlab_request.labels(integration=INTEGRATION_NAME).inc()
|
343
|
-
|
320
|
+
try:
|
321
|
+
group.members.create({
|
322
|
+
"user_id": gitlab_user.id,
|
323
|
+
"access_level": user.access_level,
|
324
|
+
})
|
325
|
+
except gitlab.exceptions.GitlabCreateError:
|
326
|
+
gitlab_request.labels(integration=INTEGRATION_NAME).inc()
|
327
|
+
member = group.members.get(user.user)
|
328
|
+
member.access_level = user.access_level
|
329
|
+
member.save()
|
344
330
|
|
345
|
-
def
|
346
|
-
gitlab_request.labels(integration=INTEGRATION_NAME).inc()
|
347
|
-
group = self.gl.groups.get(group)
|
348
|
-
user = self.get_user(username)
|
331
|
+
def remove_group_member(self, group, user_id):
|
349
332
|
gitlab_request.labels(integration=INTEGRATION_NAME).inc()
|
350
|
-
|
351
|
-
|
333
|
+
group.members.delete(user_id)
|
334
|
+
|
335
|
+
def change_access(self, member, access_level):
|
336
|
+
member.access_level = access_level
|
352
337
|
gitlab_request.labels(integration=INTEGRATION_NAME).inc()
|
353
338
|
member.save()
|
354
339
|
|
{qontract_reconcile-0.10.1rc1179.dist-info → qontract_reconcile-0.10.1rc1181.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|
File without changes
|