qontract-reconcile 0.10.2.dev481__py3-none-any.whl → 0.10.2.dev484__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.
@@ -1,323 +0,0 @@
1
- import sys
2
- from collections.abc import Callable
3
- from dataclasses import dataclass
4
- from typing import Any, Self
5
-
6
- from pydantic.main import BaseModel
7
-
8
- import reconcile.openshift_base as ob
9
- from reconcile.gql_definitions.common.app_interface_roles import (
10
- AccessV1,
11
- BotV1,
12
- ClusterV1,
13
- NamespaceV1,
14
- RoleV1,
15
- UserV1,
16
- )
17
- from reconcile.gql_definitions.common.namespaces import NamespaceV1 as CommonNamespaceV1
18
- from reconcile.typed_queries.app_interface_roles import get_app_interface_roles
19
- from reconcile.typed_queries.namespaces import get_namespaces
20
- from reconcile.utils import (
21
- expiration,
22
- )
23
- from reconcile.utils.constants import DEFAULT_THREAD_POOL_SIZE
24
- from reconcile.utils.defer import defer
25
- from reconcile.utils.openshift_resource import OpenshiftResource as OR
26
- from reconcile.utils.openshift_resource import (
27
- ResourceInventory,
28
- )
29
- from reconcile.utils.semver_helper import make_semver
30
- from reconcile.utils.sharding import is_in_shard
31
-
32
- QONTRACT_INTEGRATION = "openshift-rolebindings"
33
- QONTRACT_INTEGRATION_VERSION = make_semver(0, 3, 0)
34
-
35
-
36
- class OCResource(BaseModel, arbitrary_types_allowed=True):
37
- resource: OR
38
- resource_name: str
39
- privileged: bool
40
-
41
-
42
- @dataclass
43
- class ServiceAccountSpec:
44
- sa_namespace_name: str
45
- sa_name: str
46
-
47
- @classmethod
48
- def create_sa_spec(cls, bots: list[BotV1] | None) -> list[Self]:
49
- return [
50
- cls(
51
- sa_namespace_name=full_service_account[0],
52
- sa_name=full_service_account[1],
53
- )
54
- for bot in bots or []
55
- if bot.openshift_serviceaccount
56
- and (full_service_account := bot.openshift_serviceaccount.split("/"))
57
- and len(full_service_account) == 2
58
- ]
59
-
60
-
61
- class RoleBindingSpec(BaseModel, validate_by_alias=True, arbitrary_types_allowed=True):
62
- role_name: str
63
- role_kind: str
64
- namespace: NamespaceV1
65
- cluster: ClusterV1
66
- privileged: bool
67
- usernames: set[str]
68
- openshift_service_accounts: list[ServiceAccountSpec]
69
-
70
- def get_users_desired_state(self) -> list[dict[str, str]]:
71
- return [
72
- {"cluster": self.cluster.name, "user": username}
73
- for username in self.usernames
74
- ]
75
-
76
- @classmethod
77
- def create_role_binding_spec(
78
- cls,
79
- access: AccessV1,
80
- users: list[UserV1] | None = None,
81
- enforced_user_keys: list[str] | None = None,
82
- bots: list[BotV1] | None = None,
83
- support_role_ref: bool = False,
84
- ) -> Self | None:
85
- if not access.namespace:
86
- return None
87
- if not (access.role or access.cluster_role):
88
- return None
89
- privileged = access.namespace.cluster_admin or False
90
- auth_dict = [
91
- auth.model_dump(by_alias=True) for auth in access.namespace.cluster.auth
92
- ]
93
- usernames = RoleBindingSpec.get_usernames_from_users(
94
- users,
95
- ob.determine_user_keys_for_access(
96
- access.namespace.cluster.name,
97
- auth_dict,
98
- enforced_user_keys,
99
- ),
100
- )
101
- service_accounts = ServiceAccountSpec.create_sa_spec(bots) if bots else []
102
- role_kind = "Role" if access.role and support_role_ref else "ClusterRole"
103
- return cls(
104
- role_name=access.role or access.cluster_role,
105
- role_kind=role_kind,
106
- namespace=access.namespace,
107
- cluster=access.namespace.cluster,
108
- privileged=privileged,
109
- usernames=usernames,
110
- openshift_service_accounts=service_accounts,
111
- )
112
-
113
- @classmethod
114
- def create_rb_specs_from_role(
115
- cls,
116
- role: RoleV1,
117
- enforced_user_keys: list[str] | None = None,
118
- support_role_ref: bool = False,
119
- ) -> list[Self]:
120
- rolebinding_spec_list = [
121
- role_binding_spec
122
- for access in role.access or []
123
- if (
124
- access.namespace
125
- and is_valid_namespace(access.namespace)
126
- and (
127
- role_binding_spec := cls.create_role_binding_spec(
128
- access,
129
- role.users,
130
- enforced_user_keys,
131
- role.bots,
132
- support_role_ref,
133
- )
134
- )
135
- )
136
- ]
137
- return rolebinding_spec_list
138
-
139
- @staticmethod
140
- def get_usernames_from_users(
141
- users: list[UserV1] | None = None, user_keys: list[str] | None = None
142
- ) -> set[str]:
143
- return {
144
- name
145
- for user in users or []
146
- for user_key in user_keys or []
147
- if (name := getattr(user, user_key, None))
148
- }
149
-
150
- def construct_user_oc_resource(self, user: str) -> OCResource:
151
- name = f"{self.role_name}-{user}"
152
- body: dict[str, Any] = {
153
- "apiVersion": "rbac.authorization.k8s.io/v1",
154
- "kind": "RoleBinding",
155
- "metadata": {"name": name},
156
- "roleRef": {"kind": self.role_kind, "name": self.role_name},
157
- "subjects": [{"kind": "User", "name": user}],
158
- }
159
- return OCResource(
160
- resource=OR(
161
- body,
162
- QONTRACT_INTEGRATION,
163
- QONTRACT_INTEGRATION_VERSION,
164
- error_details=name,
165
- ),
166
- resource_name=name,
167
- privileged=self.privileged,
168
- )
169
-
170
- def get_oc_resources(self) -> list[OCResource]:
171
- user_oc_resources = [
172
- self.construct_user_oc_resource(username) for username in self.usernames
173
- ]
174
- sa_oc_resources = [
175
- self.construct_sa_oc_resource(sa.sa_namespace_name, sa.sa_name)
176
- for sa in self.openshift_service_accounts
177
- ]
178
- return user_oc_resources + sa_oc_resources
179
-
180
- def construct_sa_oc_resource(
181
- self, sa_namespace_name: str, sa_name: str
182
- ) -> OCResource:
183
- name = f"{self.role_name}-{sa_namespace_name}-{sa_name}"
184
- body: dict[str, Any] = {
185
- "apiVersion": "rbac.authorization.k8s.io/v1",
186
- "kind": "RoleBinding",
187
- "metadata": {"name": name},
188
- "roleRef": {"kind": self.role_kind, "name": self.role_name},
189
- "subjects": [
190
- {
191
- "kind": "ServiceAccount",
192
- "name": sa_name,
193
- "namespace": sa_namespace_name,
194
- }
195
- ],
196
- }
197
- return OCResource(
198
- resource=OR(
199
- body,
200
- QONTRACT_INTEGRATION,
201
- QONTRACT_INTEGRATION_VERSION,
202
- error_details=name,
203
- ),
204
- resource_name=name,
205
- privileged=self.privileged,
206
- )
207
-
208
-
209
- def construct_user_oc_resource(role: str, user: str) -> tuple[OR, str]:
210
- name = f"{role}-{user}"
211
- body = {
212
- "apiVersion": "rbac.authorization.k8s.io/v1",
213
- "kind": "RoleBinding",
214
- "metadata": {"name": name},
215
- "roleRef": {"kind": "ClusterRole", "name": role},
216
- "subjects": [{"kind": "User", "name": user}],
217
- }
218
- return (
219
- OR(
220
- body, QONTRACT_INTEGRATION, QONTRACT_INTEGRATION_VERSION, error_details=name
221
- ),
222
- name,
223
- )
224
-
225
-
226
- def construct_sa_oc_resource(role: str, namespace: str, sa_name: str) -> tuple[OR, str]:
227
- name = f"{role}-{namespace}-{sa_name}"
228
- body = {
229
- "apiVersion": "rbac.authorization.k8s.io/v1",
230
- "kind": "RoleBinding",
231
- "metadata": {"name": name},
232
- "roleRef": {"kind": "ClusterRole", "name": role},
233
- "subjects": [
234
- {"kind": "ServiceAccount", "name": sa_name, "namespace": namespace}
235
- ],
236
- }
237
- return (
238
- OR(
239
- body, QONTRACT_INTEGRATION, QONTRACT_INTEGRATION_VERSION, error_details=name
240
- ),
241
- name,
242
- )
243
-
244
-
245
- def fetch_desired_state(
246
- ri: ResourceInventory | None,
247
- support_role_ref: bool = False,
248
- enforced_user_keys: list[str] | None = None,
249
- allowed_clusters: set[str] | None = None,
250
- ) -> list[dict[str, str]]:
251
- if allowed_clusters is not None and not allowed_clusters:
252
- return []
253
- roles: list[RoleV1] = expiration.filter(get_app_interface_roles())
254
- users_desired_state: list[dict[str, str]] = []
255
- for role in roles:
256
- rolebindings: list[RoleBindingSpec] = RoleBindingSpec.create_rb_specs_from_role(
257
- role, enforced_user_keys, support_role_ref
258
- )
259
- if allowed_clusters is not None:
260
- rolebindings = [
261
- rolebinding
262
- for rolebinding in rolebindings
263
- if rolebinding.cluster.name in allowed_clusters
264
- ]
265
- for rolebinding in rolebindings:
266
- users_desired_state.extend(rolebinding.get_users_desired_state())
267
- if ri is None:
268
- continue
269
- for oc_resource in rolebinding.get_oc_resources():
270
- if not ri.get_desired(
271
- rolebinding.cluster.name,
272
- rolebinding.namespace.name,
273
- "RoleBinding.rbac.authorization.k8s.io",
274
- oc_resource.resource_name,
275
- ):
276
- ri.add_desired_resource(
277
- cluster=rolebinding.cluster.name,
278
- namespace=rolebinding.namespace.name,
279
- resource=oc_resource.resource,
280
- privileged=oc_resource.privileged,
281
- )
282
- return users_desired_state
283
-
284
-
285
- def is_valid_namespace(namespace: NamespaceV1 | CommonNamespaceV1) -> bool:
286
- return (
287
- bool(namespace.managed_roles)
288
- and is_in_shard(f"{namespace.cluster.name}/{namespace.name}")
289
- and not ob.is_namespace_deleted(namespace.model_dump(by_alias=True))
290
- )
291
-
292
-
293
- @defer
294
- def run(
295
- dry_run: bool,
296
- thread_pool_size: int = DEFAULT_THREAD_POOL_SIZE,
297
- internal: bool | None = None,
298
- use_jump_host: bool = True,
299
- support_role_ref: bool = False,
300
- defer: Callable | None = None,
301
- ) -> None:
302
- namespaces = [
303
- namespace.model_dump(by_alias=True, exclude={"openshift_resources"})
304
- for namespace in get_namespaces()
305
- if is_valid_namespace(namespace)
306
- ]
307
- ri, oc_map = ob.fetch_current_state(
308
- namespaces=namespaces,
309
- thread_pool_size=thread_pool_size,
310
- integration=QONTRACT_INTEGRATION,
311
- integration_version=QONTRACT_INTEGRATION_VERSION,
312
- override_managed_types=["RoleBinding.rbac.authorization.k8s.io"],
313
- internal=internal,
314
- use_jump_host=use_jump_host,
315
- )
316
- if defer:
317
- defer(oc_map.cleanup)
318
- fetch_desired_state(ri, support_role_ref, allowed_clusters=set(oc_map.clusters()))
319
- ob.publish_metrics(ri, QONTRACT_INTEGRATION)
320
- ob.realize_data(dry_run, oc_map, ri, thread_pool_size)
321
-
322
- if ri.has_error_registered():
323
- sys.exit(1)