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.
- {qontract_reconcile-0.10.2.dev481.dist-info → qontract_reconcile-0.10.2.dev484.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.2.dev481.dist-info → qontract_reconcile-0.10.2.dev484.dist-info}/RECORD +14 -8
- reconcile/cli.py +31 -16
- reconcile/gql_definitions/common/app_interface_clusterrole.py +104 -0
- reconcile/openshift_bindings/__init__.py +0 -0
- reconcile/openshift_bindings/constants.py +7 -0
- reconcile/openshift_bindings/models.py +264 -0
- reconcile/openshift_bindings/openshift_clusterrolebindings.py +129 -0
- reconcile/openshift_bindings/openshift_rolebindings.py +135 -0
- reconcile/openshift_bindings/utils.py +14 -0
- reconcile/openshift_users.py +50 -5
- reconcile/typed_queries/app_interface_clusterroles.py +10 -0
- reconcile/openshift_clusterrolebindings.py +0 -192
- reconcile/openshift_rolebindings.py +0 -323
- {qontract_reconcile-0.10.2.dev481.dist-info → qontract_reconcile-0.10.2.dev484.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev481.dist-info → qontract_reconcile-0.10.2.dev484.dist-info}/entry_points.txt +0 -0
|
@@ -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)
|
{qontract_reconcile-0.10.2.dev481.dist-info → qontract_reconcile-0.10.2.dev484.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|