qontract-reconcile 0.10.1rc349__py3-none-any.whl → 0.10.1rc350__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.1rc349.dist-info → qontract_reconcile-0.10.1rc350.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc349.dist-info → qontract_reconcile-0.10.1rc350.dist-info}/RECORD +19 -15
- reconcile/aus/advanced_upgrade_service.py +50 -13
- reconcile/aus/aus_label_source.py +115 -0
- reconcile/cli.py +4 -4
- reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py +11 -1
- reconcile/gql_definitions/advanced_upgrade_service/aus_organization.py +9 -1
- reconcile/gql_definitions/fragments/aus_organization.py +9 -11
- reconcile/gql_definitions/fragments/minimal_ocm_organization.py +29 -0
- reconcile/gql_definitions/{ocm_subscription_labels → ocm_labels}/clusters.py +8 -0
- reconcile/gql_definitions/ocm_labels/organizations.py +72 -0
- reconcile/ocm_labels/integration.py +406 -0
- reconcile/ocm_labels/label_sources.py +76 -0
- reconcile/utils/ocm/labels.py +61 -1
- reconcile/ocm_subscription_labels/integration.py +0 -250
- {qontract_reconcile-0.10.1rc349.dist-info → qontract_reconcile-0.10.1rc350.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc349.dist-info → qontract_reconcile-0.10.1rc350.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc349.dist-info → qontract_reconcile-0.10.1rc350.dist-info}/top_level.txt +0 -0
- /reconcile/gql_definitions/{ocm_subscription_labels → ocm_labels}/__init__.py +0 -0
- /reconcile/{ocm_subscription_labels → ocm_labels}/__init__.py +0 -0
@@ -0,0 +1,406 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import logging
|
4
|
+
from collections.abc import (
|
5
|
+
Callable,
|
6
|
+
Iterable,
|
7
|
+
)
|
8
|
+
from typing import (
|
9
|
+
Any,
|
10
|
+
Optional,
|
11
|
+
)
|
12
|
+
|
13
|
+
from deepdiff import DeepHash
|
14
|
+
from pydantic import validator
|
15
|
+
|
16
|
+
from reconcile.aus.aus_label_source import (
|
17
|
+
init_aus_cluster_label_source,
|
18
|
+
init_aus_org_label_source,
|
19
|
+
)
|
20
|
+
from reconcile.gql_definitions.common.ocm_environments import (
|
21
|
+
query as ocm_environment_query,
|
22
|
+
)
|
23
|
+
from reconcile.gql_definitions.fragments.ocm_environment import OCMEnvironment
|
24
|
+
from reconcile.gql_definitions.ocm_labels.clusters import ClusterV1
|
25
|
+
from reconcile.gql_definitions.ocm_labels.clusters import query as cluster_query
|
26
|
+
from reconcile.gql_definitions.ocm_labels.organizations import OpenShiftClusterManagerV1
|
27
|
+
from reconcile.gql_definitions.ocm_labels.organizations import (
|
28
|
+
query as organization_query,
|
29
|
+
)
|
30
|
+
from reconcile.ocm_labels.label_sources import (
|
31
|
+
ClusterRef,
|
32
|
+
LabelSource,
|
33
|
+
LabelState,
|
34
|
+
OrgRef,
|
35
|
+
)
|
36
|
+
from reconcile.utils import gql
|
37
|
+
from reconcile.utils.differ import diff_mappings
|
38
|
+
from reconcile.utils.disabled_integrations import integration_is_enabled
|
39
|
+
from reconcile.utils.helpers import flatten
|
40
|
+
from reconcile.utils.ocm.clusters import discover_clusters_for_organizations
|
41
|
+
from reconcile.utils.ocm.labels import (
|
42
|
+
add_label,
|
43
|
+
build_organization_labels_href,
|
44
|
+
delete_label,
|
45
|
+
get_org_labels,
|
46
|
+
update_label,
|
47
|
+
)
|
48
|
+
from reconcile.utils.ocm.search_filters import Filter
|
49
|
+
from reconcile.utils.ocm_base_client import (
|
50
|
+
OCMAPIClientConfigurationProtocol,
|
51
|
+
OCMBaseClient,
|
52
|
+
init_ocm_base_client,
|
53
|
+
)
|
54
|
+
from reconcile.utils.runtime.integration import (
|
55
|
+
PydanticRunParams,
|
56
|
+
QontractReconcileIntegration,
|
57
|
+
)
|
58
|
+
from reconcile.utils.secret_reader import SecretReaderBase
|
59
|
+
|
60
|
+
QONTRACT_INTEGRATION = "ocm-labels"
|
61
|
+
|
62
|
+
|
63
|
+
class OcmLabelsIntegrationParams(PydanticRunParams):
|
64
|
+
managed_label_prefixes: list[str] = []
|
65
|
+
|
66
|
+
@validator("managed_label_prefixes")
|
67
|
+
def must_end_with_dot( # pylint: disable=no-self-argument
|
68
|
+
cls, v: list[str]
|
69
|
+
) -> list[str]:
|
70
|
+
return [prefix + "." if not prefix.endswith(".") else prefix for prefix in v]
|
71
|
+
|
72
|
+
|
73
|
+
class ManagedLabelConflictError(Exception):
|
74
|
+
pass
|
75
|
+
|
76
|
+
|
77
|
+
class OcmLabelsIntegration(QontractReconcileIntegration[OcmLabelsIntegrationParams]):
|
78
|
+
"""Sync labels to subscription and organizations."""
|
79
|
+
|
80
|
+
@property
|
81
|
+
def name(self) -> str:
|
82
|
+
return QONTRACT_INTEGRATION
|
83
|
+
|
84
|
+
def get_clusters(self, query_func: Callable) -> list[ClusterV1]:
|
85
|
+
data = cluster_query(query_func)
|
86
|
+
return [
|
87
|
+
c
|
88
|
+
for c in data.clusters or []
|
89
|
+
if c.ocm is not None and integration_is_enabled(self.name, c)
|
90
|
+
]
|
91
|
+
|
92
|
+
def get_organizations(
|
93
|
+
self, query_func: Callable
|
94
|
+
) -> list[OpenShiftClusterManagerV1]:
|
95
|
+
return organization_query(query_func).organizations or []
|
96
|
+
|
97
|
+
def get_environments(self, query_func: Callable) -> list[OCMEnvironment]:
|
98
|
+
return ocm_environment_query(query_func).environments
|
99
|
+
|
100
|
+
def init_ocm_apis(
|
101
|
+
self,
|
102
|
+
environments: Iterable[OCMEnvironment],
|
103
|
+
init_ocm_base_client: Callable[
|
104
|
+
[OCMAPIClientConfigurationProtocol, SecretReaderBase], OCMBaseClient
|
105
|
+
] = init_ocm_base_client,
|
106
|
+
) -> dict[str, OCMBaseClient]:
|
107
|
+
return {
|
108
|
+
env.name: init_ocm_base_client(env, self.secret_reader)
|
109
|
+
for env in environments
|
110
|
+
}
|
111
|
+
|
112
|
+
def org_label_sources(self, query_func: Callable) -> list[LabelSource]:
|
113
|
+
return [
|
114
|
+
init_aus_org_label_source(query_func),
|
115
|
+
]
|
116
|
+
|
117
|
+
def subscription_label_sources(
|
118
|
+
self, clusters: list[ClusterV1], query_func: Callable
|
119
|
+
) -> list[LabelSource]:
|
120
|
+
return [
|
121
|
+
init_cluster_subscription_label_source(clusters),
|
122
|
+
init_aus_cluster_label_source(query_func),
|
123
|
+
]
|
124
|
+
|
125
|
+
def get_early_exit_desired_state(self) -> Optional[dict[str, Any]]:
|
126
|
+
gqlapi = gql.get_api()
|
127
|
+
desired = {
|
128
|
+
"org_labels": self.fetch_desired_state(
|
129
|
+
self.org_label_sources(gqlapi.query)
|
130
|
+
),
|
131
|
+
"subs_labels": self.fetch_desired_state(
|
132
|
+
self.subscription_label_sources(
|
133
|
+
self.get_clusters(gqlapi.query), gqlapi.query
|
134
|
+
)
|
135
|
+
),
|
136
|
+
}
|
137
|
+
# to figure out wheter to run a PR check of to exit early, a hash value
|
138
|
+
# of the desired state is sufficient
|
139
|
+
return {"hash": DeepHash(desired).get(desired)}
|
140
|
+
|
141
|
+
def run(self, dry_run: bool) -> None:
|
142
|
+
gqlapi = gql.get_api()
|
143
|
+
self.get_early_exit_desired_state()
|
144
|
+
clusters = self.get_clusters(gqlapi.query)
|
145
|
+
organizations = self.get_organizations(gqlapi.query)
|
146
|
+
environments = self.get_environments(gqlapi.query)
|
147
|
+
|
148
|
+
self.ocm_apis = self.init_ocm_apis(environments, init_ocm_base_client)
|
149
|
+
|
150
|
+
# organization labels
|
151
|
+
orgs_current_state, orgs_desired_state = self.fetch_organization_label_states(
|
152
|
+
organizations, gqlapi.query
|
153
|
+
)
|
154
|
+
self.reconcile(
|
155
|
+
dry_run=dry_run,
|
156
|
+
scope="organization",
|
157
|
+
current_state=orgs_current_state,
|
158
|
+
desired_state=orgs_desired_state,
|
159
|
+
)
|
160
|
+
|
161
|
+
# subscription labels
|
162
|
+
subs_current_state, subs_desired_state = self.fetch_subscription_label_states(
|
163
|
+
clusters, gqlapi.query
|
164
|
+
)
|
165
|
+
self.reconcile(
|
166
|
+
dry_run=dry_run,
|
167
|
+
scope="cluster",
|
168
|
+
current_state=subs_current_state,
|
169
|
+
desired_state=subs_desired_state,
|
170
|
+
)
|
171
|
+
|
172
|
+
def fetch_organization_label_states(
|
173
|
+
self, organizations: Iterable[OpenShiftClusterManagerV1], query_func: Callable
|
174
|
+
) -> tuple[LabelState, LabelState]:
|
175
|
+
"""
|
176
|
+
Returns the current and desired state of the organizations labels for
|
177
|
+
the given organizations.
|
178
|
+
|
179
|
+
Please note that the current state might not contain all requested organizations,
|
180
|
+
e.g. if a organization can't be found in OCM.
|
181
|
+
"""
|
182
|
+
current_state = self.fetch_organization_label_current_state(
|
183
|
+
organizations, self.params.managed_label_prefixes
|
184
|
+
)
|
185
|
+
desired_state = self.fetch_desired_state(self.org_label_sources(query_func))
|
186
|
+
return current_state, desired_state
|
187
|
+
|
188
|
+
def fetch_organization_label_current_state(
|
189
|
+
self,
|
190
|
+
organizations: Iterable[OpenShiftClusterManagerV1],
|
191
|
+
managed_label_prefixes: list[str],
|
192
|
+
) -> LabelState:
|
193
|
+
"""
|
194
|
+
Fetches the current state of organizations labels for the given organizations.
|
195
|
+
If an organization can't be found in OCM, the resulting dict will not contain a
|
196
|
+
state for it, not even an empty one.
|
197
|
+
|
198
|
+
Only labels with a prefix in managed_label_prefixes are returned. Not every label
|
199
|
+
on an organizations is this integrations business.
|
200
|
+
"""
|
201
|
+
states: LabelState = {
|
202
|
+
OrgRef(
|
203
|
+
org_id=org.org_id,
|
204
|
+
ocm_env=org.environment.name,
|
205
|
+
label_container_href=build_organization_labels_href(org.org_id),
|
206
|
+
name=org.name,
|
207
|
+
): {}
|
208
|
+
for org in organizations
|
209
|
+
}
|
210
|
+
|
211
|
+
# prepare search filters
|
212
|
+
managed_label_filter = Filter()
|
213
|
+
for prefix in managed_label_prefixes:
|
214
|
+
managed_label_filter |= Filter().like("key", f"{prefix}%")
|
215
|
+
|
216
|
+
for env_name, ocm_api in self.ocm_apis.items():
|
217
|
+
env_orgs = {
|
218
|
+
o.org_id: o for o in organizations if o.environment.name == env_name
|
219
|
+
}
|
220
|
+
if not env_orgs:
|
221
|
+
continue
|
222
|
+
labels_by_org_id = get_org_labels(
|
223
|
+
ocm_api=ocm_api,
|
224
|
+
org_ids=set(env_orgs.keys()),
|
225
|
+
label_filter=managed_label_filter,
|
226
|
+
)
|
227
|
+
for org_id, labels in labels_by_org_id.items():
|
228
|
+
states[
|
229
|
+
OrgRef(
|
230
|
+
org_id=org_id,
|
231
|
+
ocm_env=env_name,
|
232
|
+
label_container_href=build_organization_labels_href(org_id),
|
233
|
+
name=env_orgs[org_id].name,
|
234
|
+
)
|
235
|
+
] = labels.get_values_dict()
|
236
|
+
|
237
|
+
return states
|
238
|
+
|
239
|
+
def fetch_subscription_label_states(
|
240
|
+
self, clusters: list[ClusterV1], query_func: Callable
|
241
|
+
) -> tuple[LabelState, LabelState]:
|
242
|
+
"""
|
243
|
+
Returns the current and desired state of the subscription labels for
|
244
|
+
the given clusters.
|
245
|
+
|
246
|
+
Please note that the current state might not contain all requested clusters,
|
247
|
+
e.g. if a cluster can't be found in OCM or is not considered ready yet.
|
248
|
+
"""
|
249
|
+
current_state = self.fetch_subscription_label_current_state(
|
250
|
+
clusters, self.params.managed_label_prefixes
|
251
|
+
)
|
252
|
+
desired_state = self.fetch_desired_state(
|
253
|
+
self.subscription_label_sources(clusters, query_func)
|
254
|
+
)
|
255
|
+
return current_state, desired_state
|
256
|
+
|
257
|
+
def fetch_subscription_label_current_state(
|
258
|
+
self, clusters: Iterable[ClusterV1], managed_label_prefixes: list[str]
|
259
|
+
) -> LabelState:
|
260
|
+
"""
|
261
|
+
Fetches the current state of subscription labels for the given clusters.
|
262
|
+
If a cluster can't be found in OCM, the resulting dict will not contain a
|
263
|
+
state for it, not even an empty one.
|
264
|
+
|
265
|
+
Only labels with a prefix in managed_label_prefixes are returned. Not every label
|
266
|
+
on a subscription is this integrations business.
|
267
|
+
"""
|
268
|
+
cluster_ids = {c.spec.q_id for c in clusters if c.spec and c.spec.q_id}
|
269
|
+
states: LabelState = {}
|
270
|
+
for env_name, ocm_api in self.ocm_apis.items():
|
271
|
+
for cluster_details in discover_clusters_for_organizations(
|
272
|
+
ocm_api=ocm_api,
|
273
|
+
organization_ids=list(
|
274
|
+
{
|
275
|
+
c.ocm.org_id
|
276
|
+
for c in clusters
|
277
|
+
if c.ocm and c.ocm.environment.name == env_name
|
278
|
+
}
|
279
|
+
),
|
280
|
+
):
|
281
|
+
if cluster_details.ocm_cluster.id not in cluster_ids:
|
282
|
+
# there might be more clusters in an organization than we care about
|
283
|
+
continue
|
284
|
+
|
285
|
+
filtered_labels = {
|
286
|
+
label: value
|
287
|
+
for label, value in cluster_details.subscription_labels.get_values_dict().items()
|
288
|
+
if label.startswith(tuple(managed_label_prefixes))
|
289
|
+
}
|
290
|
+
states[
|
291
|
+
ClusterRef(
|
292
|
+
cluster_id=cluster_details.ocm_cluster.id,
|
293
|
+
org_id=cluster_details.organization_id,
|
294
|
+
ocm_env=env_name,
|
295
|
+
name=cluster_details.ocm_cluster.name,
|
296
|
+
label_container_href=f"{cluster_details.ocm_cluster.subscription.href}/labels",
|
297
|
+
)
|
298
|
+
] = filtered_labels
|
299
|
+
return states
|
300
|
+
|
301
|
+
def fetch_desired_state(self, sources: list[LabelSource]) -> LabelState:
|
302
|
+
states: LabelState = {}
|
303
|
+
for s in sources:
|
304
|
+
for owner_ref, labels in s.get_labels().items():
|
305
|
+
if owner_ref not in states:
|
306
|
+
states[owner_ref] = {}
|
307
|
+
for label, value in labels.items():
|
308
|
+
if label in states[owner_ref]:
|
309
|
+
raise ManagedLabelConflictError(
|
310
|
+
f"The label {label} on {owner_ref.identity_labels()} is already managed by another label source"
|
311
|
+
)
|
312
|
+
states[owner_ref][label] = value
|
313
|
+
|
314
|
+
return states
|
315
|
+
|
316
|
+
def reconcile(
|
317
|
+
self,
|
318
|
+
dry_run: bool,
|
319
|
+
scope: str,
|
320
|
+
current_state: LabelState,
|
321
|
+
desired_state: LabelState,
|
322
|
+
) -> None:
|
323
|
+
# we iterate via the current state because it refers to the clusters we can act on
|
324
|
+
for label_owner_ref, current_labels in current_state.items():
|
325
|
+
ocm_api = self.ocm_apis[label_owner_ref.ocm_env]
|
326
|
+
desired_labels = desired_state.get(label_owner_ref, {})
|
327
|
+
if current_labels == desired_labels:
|
328
|
+
continue
|
329
|
+
|
330
|
+
diff_result = diff_mappings(current_labels, desired_labels)
|
331
|
+
|
332
|
+
for label_to_add, value in diff_result.add.items():
|
333
|
+
logging.info(
|
334
|
+
[
|
335
|
+
f"create_{scope}_label",
|
336
|
+
*label_owner_ref.identity_labels(),
|
337
|
+
f"{label_to_add}={value}",
|
338
|
+
]
|
339
|
+
)
|
340
|
+
if not dry_run:
|
341
|
+
add_label(
|
342
|
+
ocm_api=ocm_api,
|
343
|
+
label_container_href=label_owner_ref.required_label_container_href(),
|
344
|
+
label=label_to_add,
|
345
|
+
value=value,
|
346
|
+
)
|
347
|
+
for label_to_rm, value in diff_result.delete.items():
|
348
|
+
logging.info(
|
349
|
+
[
|
350
|
+
f"delete_{scope}_label",
|
351
|
+
*label_owner_ref.identity_labels(),
|
352
|
+
f"{label_to_rm}={value}",
|
353
|
+
]
|
354
|
+
)
|
355
|
+
if not dry_run:
|
356
|
+
delete_label(
|
357
|
+
ocm_api=ocm_api,
|
358
|
+
label_container_href=label_owner_ref.required_label_container_href(),
|
359
|
+
label=label_to_rm,
|
360
|
+
)
|
361
|
+
for label_to_update, diff_pair in diff_result.change.items():
|
362
|
+
value = diff_pair.desired
|
363
|
+
logging.info(
|
364
|
+
[
|
365
|
+
f"update_{scope}_label",
|
366
|
+
*label_owner_ref.identity_labels(),
|
367
|
+
f"{label_to_update}={value}",
|
368
|
+
]
|
369
|
+
)
|
370
|
+
if not dry_run:
|
371
|
+
update_label(
|
372
|
+
ocm_api=ocm_api,
|
373
|
+
label_container_href=label_owner_ref.required_label_container_href(),
|
374
|
+
label=label_to_update,
|
375
|
+
value=value,
|
376
|
+
)
|
377
|
+
|
378
|
+
|
379
|
+
def init_cluster_subscription_label_source(
|
380
|
+
clusters: list[ClusterV1],
|
381
|
+
) -> ClusterSubscriptionLabelSource:
|
382
|
+
return ClusterSubscriptionLabelSource(
|
383
|
+
clusters=[
|
384
|
+
c
|
385
|
+
for c in clusters or []
|
386
|
+
if c.ocm is not None and integration_is_enabled(QONTRACT_INTEGRATION, c)
|
387
|
+
],
|
388
|
+
)
|
389
|
+
|
390
|
+
|
391
|
+
class ClusterSubscriptionLabelSource(LabelSource):
|
392
|
+
def __init__(self, clusters: Iterable[ClusterV1]) -> None:
|
393
|
+
self.clusters = clusters
|
394
|
+
|
395
|
+
def get_labels(self) -> LabelState:
|
396
|
+
return {
|
397
|
+
ClusterRef(
|
398
|
+
cluster_id=cluster.spec.q_id,
|
399
|
+
org_id=cluster.ocm.org_id,
|
400
|
+
ocm_env=cluster.ocm.environment.name,
|
401
|
+
name=cluster.name,
|
402
|
+
label_container_href=None,
|
403
|
+
): flatten(cluster.ocm_subscription_labels or {})
|
404
|
+
for cluster in self.clusters
|
405
|
+
if cluster.ocm and cluster.spec and cluster.spec.q_id
|
406
|
+
}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
from abc import (
|
2
|
+
ABC,
|
3
|
+
abstractmethod,
|
4
|
+
)
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from typing import Optional
|
7
|
+
|
8
|
+
|
9
|
+
@dataclass(frozen=True)
|
10
|
+
class LabelOwnerRef(ABC):
|
11
|
+
ocm_env: str
|
12
|
+
label_container_href: Optional[str]
|
13
|
+
|
14
|
+
@abstractmethod
|
15
|
+
def identity_labels(self) -> list[str]:
|
16
|
+
pass
|
17
|
+
|
18
|
+
def required_label_container_href(self) -> str:
|
19
|
+
if self.label_container_href is None:
|
20
|
+
raise ValueError(
|
21
|
+
"label_container_href is missing - this method should probably not be called in this state"
|
22
|
+
)
|
23
|
+
return self.label_container_href
|
24
|
+
|
25
|
+
|
26
|
+
@dataclass(frozen=True)
|
27
|
+
class OrgRef(LabelOwnerRef):
|
28
|
+
org_id: str
|
29
|
+
name: str
|
30
|
+
|
31
|
+
def __eq__(self, other: object) -> bool:
|
32
|
+
return (
|
33
|
+
isinstance(other, OrgRef)
|
34
|
+
and self.org_id == other.org_id
|
35
|
+
and self.ocm_env == other.ocm_env
|
36
|
+
)
|
37
|
+
|
38
|
+
def __hash__(self) -> int:
|
39
|
+
return hash((self.org_id, self.ocm_env))
|
40
|
+
|
41
|
+
def identity_labels(self) -> list[str]:
|
42
|
+
return [f"org_id={self.org_id}", f"org_name={self.name}"]
|
43
|
+
|
44
|
+
|
45
|
+
@dataclass(frozen=True)
|
46
|
+
class ClusterRef(LabelOwnerRef):
|
47
|
+
cluster_id: str
|
48
|
+
org_id: str
|
49
|
+
name: str
|
50
|
+
|
51
|
+
def __eq__(self, other: object) -> bool:
|
52
|
+
return (
|
53
|
+
isinstance(other, ClusterRef)
|
54
|
+
and self.cluster_id == other.cluster_id
|
55
|
+
and self.org_id == other.org_id
|
56
|
+
and self.ocm_env == other.ocm_env
|
57
|
+
)
|
58
|
+
|
59
|
+
def __hash__(self) -> int:
|
60
|
+
return hash((self.cluster_id, self.org_id, self.ocm_env))
|
61
|
+
|
62
|
+
def identity_labels(self) -> list[str]:
|
63
|
+
return [
|
64
|
+
f"org_id={self.org_id}",
|
65
|
+
f"cluster_id={self.cluster_id}",
|
66
|
+
f"cluster_name={self.name}",
|
67
|
+
]
|
68
|
+
|
69
|
+
|
70
|
+
LabelState = dict[LabelOwnerRef, dict[str, str]]
|
71
|
+
|
72
|
+
|
73
|
+
class LabelSource(ABC):
|
74
|
+
@abstractmethod
|
75
|
+
def get_labels(self) -> LabelState:
|
76
|
+
pass
|
reconcile/utils/ocm/labels.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
from collections import defaultdict
|
1
2
|
from collections.abc import Generator
|
2
3
|
from typing import (
|
3
4
|
Any,
|
@@ -11,6 +12,7 @@ from reconcile.utils.ocm.base import (
|
|
11
12
|
OCMLabel,
|
12
13
|
OCMOrganizationLabel,
|
13
14
|
OCMSubscriptionLabel,
|
15
|
+
build_label_container,
|
14
16
|
)
|
15
17
|
from reconcile.utils.ocm.search_filters import Filter
|
16
18
|
from reconcile.utils.ocm_base_client import OCMBaseClient
|
@@ -34,10 +36,25 @@ def add_subscription_label(
|
|
34
36
|
ocm_cluster: OCMCluster,
|
35
37
|
label: str,
|
36
38
|
value: str,
|
39
|
+
) -> None:
|
40
|
+
"""Add the given label to the cluster subscription."""
|
41
|
+
add_label(
|
42
|
+
ocm_api=ocm_api,
|
43
|
+
label_container_href=f"{ocm_cluster.subscription.href}/labels",
|
44
|
+
label=label,
|
45
|
+
value=value,
|
46
|
+
)
|
47
|
+
|
48
|
+
|
49
|
+
def add_label(
|
50
|
+
ocm_api: OCMBaseClient,
|
51
|
+
label_container_href: str,
|
52
|
+
label: str,
|
53
|
+
value: str,
|
37
54
|
) -> None:
|
38
55
|
"""Add the given label to the cluster subscription."""
|
39
56
|
ocm_api.post(
|
40
|
-
api_path=
|
57
|
+
api_path=label_container_href,
|
41
58
|
data={"kind": "Label", "key": label, "value": value},
|
42
59
|
)
|
43
60
|
|
@@ -54,11 +71,29 @@ def update_ocm_label(
|
|
54
71
|
)
|
55
72
|
|
56
73
|
|
74
|
+
def update_label(
|
75
|
+
ocm_api: OCMBaseClient,
|
76
|
+
label_container_href: str,
|
77
|
+
label: str,
|
78
|
+
value: str,
|
79
|
+
) -> None:
|
80
|
+
"""Update the label value in the given OCM label."""
|
81
|
+
ocm_api.patch(
|
82
|
+
api_path=f"{label_container_href}/{label}",
|
83
|
+
data={"kind": "Label", "key": label, "value": value},
|
84
|
+
)
|
85
|
+
|
86
|
+
|
57
87
|
def delete_ocm_label(ocm_api: OCMBaseClient, ocm_label: OCMLabel) -> None:
|
58
88
|
"""Delete the given OCM label."""
|
59
89
|
ocm_api.delete(api_path=ocm_label.href)
|
60
90
|
|
61
91
|
|
92
|
+
def delete_label(ocm_api: OCMBaseClient, label_container_href: str, label: str) -> None:
|
93
|
+
"""Delete the given OCM label."""
|
94
|
+
ocm_api.delete(api_path=f"{label_container_href}/{label}")
|
95
|
+
|
96
|
+
|
62
97
|
def subscription_label_filter() -> Filter:
|
63
98
|
"""
|
64
99
|
Returns a filter that can be used to find only subscription labels.
|
@@ -145,3 +180,28 @@ def label_filter(key: str, value: Optional[str] = None) -> Filter:
|
|
145
180
|
if value:
|
146
181
|
return lf.eq("value", value)
|
147
182
|
return lf
|
183
|
+
|
184
|
+
|
185
|
+
def get_org_labels(
|
186
|
+
ocm_api: OCMBaseClient, org_ids: set[str], label_filter: Optional[Filter]
|
187
|
+
) -> dict[str, LabelContainer]:
|
188
|
+
"""
|
189
|
+
Fetch all labels from organizations. Optionally, label filtering can be
|
190
|
+
performed via the `label_filter` parameter.
|
191
|
+
|
192
|
+
The result is a dict with organization IDs as keys and label containers as values.
|
193
|
+
"""
|
194
|
+
filter = Filter().is_in("organization_id", org_ids)
|
195
|
+
if label_filter:
|
196
|
+
filter &= label_filter
|
197
|
+
labels_by_org: dict[str, list[OCMOrganizationLabel]] = defaultdict(list)
|
198
|
+
for label in get_organization_labels(ocm_api, filter):
|
199
|
+
labels_by_org[label.organization_id].append(label)
|
200
|
+
return {
|
201
|
+
org_id: build_label_container(labels)
|
202
|
+
for org_id, labels in labels_by_org.items()
|
203
|
+
}
|
204
|
+
|
205
|
+
|
206
|
+
def build_organization_labels_href(org_id: str) -> str:
|
207
|
+
return f"/api/accounts_mgmt/v1/organizations/{org_id}/labels"
|