qontract-reconcile 0.10.1rc348__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.1rc348.dist-info → qontract_reconcile-0.10.1rc350.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc348.dist-info → qontract_reconcile-0.10.1rc350.dist-info}/RECORD +24 -20
- reconcile/aus/advanced_upgrade_service.py +50 -13
- reconcile/aus/aus_label_source.py +115 -0
- reconcile/cli.py +4 -4
- reconcile/glitchtip/integration.py +64 -8
- reconcile/glitchtip/reconciler.py +1 -3
- 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/glitchtip/glitchtip_project.py +11 -2
- reconcile/gql_definitions/{ocm_subscription_labels → ocm_labels}/clusters.py +8 -0
- reconcile/gql_definitions/ocm_labels/organizations.py +72 -0
- reconcile/ldap_groups/integration.py +2 -1
- reconcile/ocm_labels/integration.py +406 -0
- reconcile/ocm_labels/label_sources.py +76 -0
- reconcile/utils/ocm/labels.py +61 -1
- tools/glitchtip_access_revalidation.py +1 -1
- reconcile/ocm_subscription_labels/integration.py +0 -250
- {qontract_reconcile-0.10.1rc348.dist-info → qontract_reconcile-0.10.1rc350.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc348.dist-info → qontract_reconcile-0.10.1rc350.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc348.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
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"
|
@@ -64,7 +64,7 @@ def main(
|
|
64
64
|
glitchtip_project_query(query_func=gql.get_api().query).glitchtip_projects or []
|
65
65
|
)
|
66
66
|
|
67
|
-
apps = {project.app.path for project in glitchtip_projects}
|
67
|
+
apps = {project.app.path for project in glitchtip_projects if project.app}
|
68
68
|
|
69
69
|
notification = Notification(
|
70
70
|
notification_type="Action Required",
|
@@ -1,250 +0,0 @@
|
|
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 pydantic import (
|
14
|
-
BaseModel,
|
15
|
-
validator,
|
16
|
-
)
|
17
|
-
|
18
|
-
from reconcile.gql_definitions.fragments.ocm_environment import OCMEnvironment
|
19
|
-
from reconcile.gql_definitions.ocm_subscription_labels.clusters import ClusterV1
|
20
|
-
from reconcile.gql_definitions.ocm_subscription_labels.clusters import (
|
21
|
-
query as cluster_query,
|
22
|
-
)
|
23
|
-
from reconcile.utils import gql
|
24
|
-
from reconcile.utils.differ import diff_mappings
|
25
|
-
from reconcile.utils.disabled_integrations import integration_is_enabled
|
26
|
-
from reconcile.utils.helpers import flatten
|
27
|
-
from reconcile.utils.ocm.clusters import (
|
28
|
-
ClusterDetails,
|
29
|
-
discover_clusters_for_organizations,
|
30
|
-
)
|
31
|
-
from reconcile.utils.ocm.labels import (
|
32
|
-
add_subscription_label,
|
33
|
-
delete_ocm_label,
|
34
|
-
update_ocm_label,
|
35
|
-
)
|
36
|
-
from reconcile.utils.ocm_base_client import (
|
37
|
-
OCMAPIClientConfigurationProtocol,
|
38
|
-
OCMBaseClient,
|
39
|
-
init_ocm_base_client,
|
40
|
-
)
|
41
|
-
from reconcile.utils.runtime.integration import (
|
42
|
-
PydanticRunParams,
|
43
|
-
QontractReconcileIntegration,
|
44
|
-
)
|
45
|
-
from reconcile.utils.secret_reader import SecretReaderBase
|
46
|
-
|
47
|
-
QONTRACT_INTEGRATION = "ocm-subscription-labels"
|
48
|
-
|
49
|
-
|
50
|
-
class EnvWithClusters(BaseModel):
|
51
|
-
env: OCMEnvironment
|
52
|
-
clusters: list[ClusterV1] = []
|
53
|
-
|
54
|
-
class Config:
|
55
|
-
arbitrary_types_allowed = True
|
56
|
-
|
57
|
-
|
58
|
-
class ClusterLabelState(BaseModel):
|
59
|
-
env: OCMEnvironment
|
60
|
-
ocm_api: OCMBaseClient
|
61
|
-
cluster_details: Optional[ClusterDetails] = None
|
62
|
-
labels: dict[str, str] = {}
|
63
|
-
|
64
|
-
class Config:
|
65
|
-
arbitrary_types_allowed = True
|
66
|
-
|
67
|
-
def __eq__(self, other: object) -> bool:
|
68
|
-
if not isinstance(other, ClusterLabelState):
|
69
|
-
raise NotImplementedError("Cannot compare to non ClusterState objects.")
|
70
|
-
return self.labels == other.labels
|
71
|
-
|
72
|
-
|
73
|
-
ClusterStates = dict[str, ClusterLabelState]
|
74
|
-
|
75
|
-
|
76
|
-
class OcmLabelsIntegrationParams(PydanticRunParams):
|
77
|
-
managed_label_prefixes: list[str] = []
|
78
|
-
|
79
|
-
@validator("managed_label_prefixes")
|
80
|
-
def must_end_with_dot( # pylint: disable=no-self-argument
|
81
|
-
cls, v: list[str]
|
82
|
-
) -> list[str]:
|
83
|
-
return [prefix + "." if not prefix.endswith(".") else prefix for prefix in v]
|
84
|
-
|
85
|
-
|
86
|
-
class OcmLabelsIntegration(QontractReconcileIntegration[OcmLabelsIntegrationParams]):
|
87
|
-
"""Sync cluster.ocm-labels to OCM."""
|
88
|
-
|
89
|
-
@property
|
90
|
-
def name(self) -> str:
|
91
|
-
return QONTRACT_INTEGRATION
|
92
|
-
|
93
|
-
def get_clusters(self, query_func: Callable) -> list[ClusterV1]:
|
94
|
-
data = cluster_query(query_func)
|
95
|
-
return [
|
96
|
-
c
|
97
|
-
for c in data.clusters or []
|
98
|
-
if c.ocm is not None and integration_is_enabled(self.name, c)
|
99
|
-
]
|
100
|
-
|
101
|
-
def get_ocm_environments(
|
102
|
-
self,
|
103
|
-
clusters: Iterable[ClusterV1],
|
104
|
-
) -> list[EnvWithClusters]:
|
105
|
-
envs: dict[str, EnvWithClusters] = {}
|
106
|
-
|
107
|
-
for cluster in clusters:
|
108
|
-
if cluster.ocm is None:
|
109
|
-
# already filtered out in get_clusters - make mypy happy
|
110
|
-
continue
|
111
|
-
if cluster.ocm.environment.name not in envs:
|
112
|
-
envs[cluster.ocm.environment.name] = EnvWithClusters(
|
113
|
-
env=cluster.ocm.environment, clusters=[cluster]
|
114
|
-
)
|
115
|
-
else:
|
116
|
-
envs[cluster.ocm.environment.name].clusters.append(cluster)
|
117
|
-
|
118
|
-
return list(envs.values())
|
119
|
-
|
120
|
-
def init_ocm_apis(
|
121
|
-
self,
|
122
|
-
envs: Iterable[EnvWithClusters],
|
123
|
-
init_ocm_base_client: Callable[
|
124
|
-
[OCMAPIClientConfigurationProtocol, SecretReaderBase], OCMBaseClient
|
125
|
-
] = init_ocm_base_client,
|
126
|
-
) -> None:
|
127
|
-
self.ocm_apis = {
|
128
|
-
env.env.name: init_ocm_base_client(env.env, self.secret_reader)
|
129
|
-
for env in envs
|
130
|
-
}
|
131
|
-
|
132
|
-
def get_early_exit_desired_state(self) -> Optional[dict[str, Any]]:
|
133
|
-
gqlapi = gql.get_api()
|
134
|
-
return {"clusters": [c.dict() for c in self.get_clusters(gqlapi.query)]}
|
135
|
-
|
136
|
-
def run(self, dry_run: bool) -> None:
|
137
|
-
gqlapi = gql.get_api()
|
138
|
-
clusters = self.get_clusters(gqlapi.query)
|
139
|
-
current_state = self.fetch_current_state(
|
140
|
-
clusters, self.params.managed_label_prefixes
|
141
|
-
)
|
142
|
-
desired_state = self.fetch_desired_state(clusters)
|
143
|
-
self.reconcile(dry_run, current_state, desired_state)
|
144
|
-
|
145
|
-
def fetch_current_state(
|
146
|
-
self, clusters: Iterable[ClusterV1], managed_label_prefixes: list[str]
|
147
|
-
) -> ClusterStates:
|
148
|
-
states: ClusterStates = {}
|
149
|
-
envs = self.get_ocm_environments(clusters)
|
150
|
-
self.init_ocm_apis(envs)
|
151
|
-
for env in envs:
|
152
|
-
for cluster_details in discover_clusters_for_organizations(
|
153
|
-
ocm_api=self.ocm_apis[env.env.name],
|
154
|
-
organization_ids=list({c.ocm.org_id for c in clusters if c.ocm}),
|
155
|
-
):
|
156
|
-
filtered_labels = {
|
157
|
-
label: value
|
158
|
-
for label, value in cluster_details.subscription_labels.get_values_dict().items()
|
159
|
-
if label.startswith(tuple(managed_label_prefixes))
|
160
|
-
}
|
161
|
-
states[cluster_details.ocm_cluster.name] = ClusterLabelState(
|
162
|
-
env=env.env,
|
163
|
-
ocm_api=self.ocm_apis[env.env.name],
|
164
|
-
cluster_details=cluster_details,
|
165
|
-
labels=filtered_labels,
|
166
|
-
)
|
167
|
-
return states
|
168
|
-
|
169
|
-
def fetch_desired_state(self, clusters: Iterable[ClusterV1]) -> ClusterStates:
|
170
|
-
states: ClusterStates = {}
|
171
|
-
for cluster in clusters:
|
172
|
-
if cluster.ocm is None:
|
173
|
-
# already filtered out in get_clusters - make mypy happy
|
174
|
-
continue
|
175
|
-
states[cluster.name] = ClusterLabelState(
|
176
|
-
env=cluster.ocm.environment,
|
177
|
-
ocm_api=self.ocm_apis[cluster.ocm.environment.name],
|
178
|
-
labels=flatten(cluster.ocm_subscription_labels or {}),
|
179
|
-
)
|
180
|
-
|
181
|
-
return states
|
182
|
-
|
183
|
-
def reconcile(
|
184
|
-
self,
|
185
|
-
dry_run: bool,
|
186
|
-
current_cluster_states: ClusterStates,
|
187
|
-
desired_cluster_states: ClusterStates,
|
188
|
-
) -> None:
|
189
|
-
for cluster_name, desired_cluster_state in desired_cluster_states.items():
|
190
|
-
try:
|
191
|
-
current_cluster_state = current_cluster_states[cluster_name]
|
192
|
-
if not (cluster_details := current_cluster_state.cluster_details):
|
193
|
-
# this should never happen - make mypy happy
|
194
|
-
raise RuntimeError("Cluster details not found.")
|
195
|
-
|
196
|
-
if desired_cluster_state == current_cluster_state:
|
197
|
-
continue
|
198
|
-
except KeyError:
|
199
|
-
logging.info(
|
200
|
-
f"Cluster '{cluster_name}' not found in OCM. Maybe it doesn't exist yet. Skipping."
|
201
|
-
)
|
202
|
-
continue
|
203
|
-
|
204
|
-
diff_result = diff_mappings(
|
205
|
-
current_cluster_state.labels, desired_cluster_state.labels
|
206
|
-
)
|
207
|
-
|
208
|
-
for label_to_add, value in diff_result.add.items():
|
209
|
-
logging.info(
|
210
|
-
[
|
211
|
-
"create_cluster_subscription_label",
|
212
|
-
cluster_name,
|
213
|
-
f"{label_to_add}={value}",
|
214
|
-
]
|
215
|
-
)
|
216
|
-
if not dry_run:
|
217
|
-
add_subscription_label(
|
218
|
-
ocm_api=desired_cluster_state.ocm_api,
|
219
|
-
ocm_cluster=cluster_details.ocm_cluster,
|
220
|
-
label=label_to_add,
|
221
|
-
value=value,
|
222
|
-
)
|
223
|
-
for label_to_rm, value in diff_result.delete.items():
|
224
|
-
logging.info(
|
225
|
-
[
|
226
|
-
"delete_cluster_subscription_label",
|
227
|
-
cluster_name,
|
228
|
-
f"{label_to_rm}={value}",
|
229
|
-
]
|
230
|
-
)
|
231
|
-
if not dry_run:
|
232
|
-
delete_ocm_label(
|
233
|
-
ocm_api=desired_cluster_state.ocm_api,
|
234
|
-
ocm_label=cluster_details.labels[label_to_rm],
|
235
|
-
)
|
236
|
-
for label_to_update, diff_pair in diff_result.change.items():
|
237
|
-
value = diff_pair.desired
|
238
|
-
logging.info(
|
239
|
-
[
|
240
|
-
"update_cluster_subscription_label",
|
241
|
-
cluster_name,
|
242
|
-
f"{label_to_update}={value}",
|
243
|
-
]
|
244
|
-
)
|
245
|
-
if not dry_run:
|
246
|
-
update_ocm_label(
|
247
|
-
ocm_api=desired_cluster_state.ocm_api,
|
248
|
-
ocm_label=cluster_details.labels[label_to_update],
|
249
|
-
value=value,
|
250
|
-
)
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc348.dist-info → qontract_reconcile-0.10.1rc350.dist-info}/top_level.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|