qontract-reconcile 0.10.2.dev155__py3-none-any.whl → 0.10.2.dev156__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qontract-reconcile
3
- Version: 0.10.2.dev155
3
+ Version: 0.10.2.dev156
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Project-URL: homepage, https://github.com/app-sre/qontract-reconcile
6
6
  Project-URL: repository, https://github.com/app-sre/qontract-reconcile
@@ -188,10 +188,10 @@ reconcile/cna/assets/asset_factory.py,sha256=7T7X_J6xIsoGETqBRI45_EyIKEdQcnRPt_G
188
188
  reconcile/cna/assets/null.py,sha256=85mVh97atCoC0aLuX47poTZiyOthmziJeBsUw0c924w,1658
189
189
  reconcile/dynatrace_token_provider/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
190
190
  reconcile/dynatrace_token_provider/dependencies.py,sha256=lvkdwqHMsn_2kgj-tUIJdTUnUNxVoS6z8k4nPkGglnQ,3129
191
- reconcile/dynatrace_token_provider/integration.py,sha256=d2iAQ_NUUIVN4I0_Y-LQQtWuWuzclJcXGQC8dEWYGvQ,26365
191
+ reconcile/dynatrace_token_provider/integration.py,sha256=1D6gIEwKvkzE9JDOcLb9EAP6JW8OK2OLNuiHAyot_XQ,28329
192
192
  reconcile/dynatrace_token_provider/metrics.py,sha256=oP-6NTZENFdvWiS0krnmX6tq3xyOzQ8e6vS0CZWYUuw,1496
193
- reconcile/dynatrace_token_provider/model.py,sha256=gkpqo5rRRueBXnIMjp4EEHqBUBuU65TRI8zpdb8GJ0A,241
194
- reconcile/dynatrace_token_provider/ocm.py,sha256=bryaK7xs7ygttUyZuSD9Up4laUhP6OUTr2VBbFiclpA,4298
193
+ reconcile/dynatrace_token_provider/model.py,sha256=L6THhpPnSIeJ5n61IHhDT_JTiSEr_uWmgJAw83RUC_w,477
194
+ reconcile/dynatrace_token_provider/ocm.py,sha256=EPknDhLXkySs8Nv8jrrl12oRoe2bRFWx_CMiHpPQhmM,3734
195
195
  reconcile/dynatrace_token_provider/validate.py,sha256=40_9QmHoB3-KBc0k_0D4QO00PpNNPS-gU9Z6cIcWga8,1920
196
196
  reconcile/endpoints_discovery/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
197
197
  reconcile/endpoints_discovery/integration.py,sha256=p50wu4OWO3RmL2A9TvjhRZkTYUe-l9aec4vgu0bxq8w,14926
@@ -797,7 +797,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
797
797
  tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
798
798
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
799
799
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
800
- qontract_reconcile-0.10.2.dev155.dist-info/METADATA,sha256=SCv6zhblOoa_txf4pnB9rfLD7oODuJ5n_FDMgerKyYU,24627
801
- qontract_reconcile-0.10.2.dev155.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
802
- qontract_reconcile-0.10.2.dev155.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
803
- qontract_reconcile-0.10.2.dev155.dist-info/RECORD,,
800
+ qontract_reconcile-0.10.2.dev156.dist-info/METADATA,sha256=j3xvjWZiv45YI5cdQQPptbUILTtSgSWO_HpQvg_0O48,24627
801
+ qontract_reconcile-0.10.2.dev156.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
802
+ qontract_reconcile-0.10.2.dev156.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
803
+ qontract_reconcile-0.10.2.dev156.dist-info/RECORD,,
@@ -13,12 +13,15 @@ from reconcile.dynatrace_token_provider.metrics import (
13
13
  DTPOrganizationErrorRate,
14
14
  DTPTokensManagedGauge,
15
15
  )
16
- from reconcile.dynatrace_token_provider.model import DynatraceAPIToken, K8sSecret
17
- from reconcile.dynatrace_token_provider.ocm import (
18
- DTP_LABEL_SEARCH,
19
- DTP_TENANT_V2_LABEL,
16
+ from reconcile.dynatrace_token_provider.model import (
20
17
  Cluster,
18
+ DynatraceAPIToken,
19
+ K8sSecret,
20
+ TokenSpecTenantBinding,
21
+ )
22
+ from reconcile.dynatrace_token_provider.ocm import (
21
23
  OCMClient,
24
+ OCMCluster,
22
25
  )
23
26
  from reconcile.dynatrace_token_provider.validate import validate_token_specs
24
27
  from reconcile.gql_definitions.dynatrace_token_provider.token_specs import (
@@ -37,6 +40,7 @@ from reconcile.utils.ocm.base import (
37
40
  OCMServiceLogSeverity,
38
41
  )
39
42
  from reconcile.utils.ocm.labels import subscription_label_filter
43
+ from reconcile.utils.ocm.sre_capability_labels import sre_capability_label_key
40
44
  from reconcile.utils.openshift_resource import (
41
45
  QONTRACT_ANNOTATION_INTEGRATION,
42
46
  QONTRACT_ANNOTATION_INTEGRATION_VERSION,
@@ -50,6 +54,9 @@ from reconcile.utils.semver_helper import make_semver
50
54
  QONTRACT_INTEGRATION_VERSION = make_semver(2, 0, 1)
51
55
  QONTRACT_INTEGRATION = "dynatrace-token-provider"
52
56
  SYNCSET_AND_MANIFEST_ID = "ext-dynatrace-tokens-dtp"
57
+ DTP_LABEL_SEARCH = sre_capability_label_key("dtp", "%")
58
+ DTP_TENANT_V2_LABEL = sre_capability_label_key("dtp.v2", "tenant")
59
+ DTP_SPEC_V2_LABEL = sre_capability_label_key("dtp.v2", "token-spec")
53
60
 
54
61
 
55
62
  class ReconcileErrorSummary(Exception):
@@ -103,6 +110,27 @@ class DynatraceTokenProviderIntegration(QontractReconcileIntegration[NoParams]):
103
110
  cnt,
104
111
  )
105
112
 
113
+ def _parse_ocm_data_to_cluster(self, ocm_cluster: OCMCluster) -> Cluster | None:
114
+ dt_tenant = ocm_cluster.labels.get(DTP_TENANT_V2_LABEL)
115
+ token_spec_name = ocm_cluster.labels.get(DTP_SPEC_V2_LABEL)
116
+ if not dt_tenant or not token_spec_name:
117
+ logging.warning(
118
+ f"[Missing DTP labels] {ocm_cluster.id=} {ocm_cluster.subscription_id=} {dt_tenant=} {token_spec_name=}"
119
+ )
120
+ return None
121
+ return Cluster(
122
+ id=ocm_cluster.id,
123
+ external_id=ocm_cluster.external_id,
124
+ organization_id=ocm_cluster.organization_id,
125
+ is_hcp=ocm_cluster.is_hcp,
126
+ dt_token_bindings=[
127
+ TokenSpecTenantBinding(
128
+ spec_name=token_spec_name,
129
+ tenant_id=dt_tenant,
130
+ )
131
+ ],
132
+ )
133
+
106
134
  def _filter_clusters(
107
135
  self,
108
136
  clusters: Iterable[Cluster],
@@ -110,18 +138,19 @@ class DynatraceTokenProviderIntegration(QontractReconcileIntegration[NoParams]):
110
138
  ) -> list[Cluster]:
111
139
  filtered_clusters = []
112
140
  for cluster in clusters:
113
- token_spec = token_spec_by_name.get(cluster.token_spec_name)
114
- if not token_spec:
115
- logging.debug(
116
- f"[{cluster.id=}] Skipping cluster. {cluster.token_spec_name=} does not exist."
117
- )
118
- continue
119
- if cluster.organization_id in token_spec.ocm_org_ids:
120
- filtered_clusters.append(cluster)
121
- else:
122
- logging.debug(
123
- f"[{cluster.id=}] Skipping cluster for {token_spec.name=}. {cluster.organization_id=} is not defined in {token_spec.ocm_org_ids=}."
124
- )
141
+ for token_binding in cluster.dt_token_bindings:
142
+ token_spec = token_spec_by_name.get(token_binding.spec_name)
143
+ if not token_spec:
144
+ logging.debug(
145
+ f"[{cluster.id=}] Skipping cluster. {token_binding.spec_name=} does not exist."
146
+ )
147
+ continue
148
+ if cluster.organization_id in token_spec.ocm_org_ids:
149
+ filtered_clusters.append(cluster)
150
+ else:
151
+ logging.debug(
152
+ f"[{cluster.id=}] Skipping cluster for {token_spec.name=}. {cluster.organization_id=} is not defined in {token_spec.ocm_org_ids=}."
153
+ )
125
154
  return filtered_clusters
126
155
 
127
156
  def reconcile(self, dry_run: bool, dependencies: Dependencies) -> None:
@@ -131,17 +160,26 @@ class DynatraceTokenProviderIntegration(QontractReconcileIntegration[NoParams]):
131
160
  with metrics.transactional_metrics(self.name):
132
161
  unhandled_exceptions = []
133
162
  for ocm_env_name, ocm_client in dependencies.ocm_client_by_env_name.items():
134
- clusters: list[Cluster] = []
163
+ ocm_clusters: list[OCMCluster] = []
135
164
  try:
136
- clusters = ocm_client.discover_clusters_by_labels(
165
+ ocm_clusters = ocm_client.discover_clusters_by_labels(
137
166
  label_filter=subscription_label_filter().like(
138
167
  "key", DTP_LABEL_SEARCH
139
168
  ),
140
169
  )
141
170
  except Exception as e:
142
171
  unhandled_exceptions.append(f"{ocm_env_name}: {e}")
143
- if not clusters:
172
+ if not ocm_clusters:
144
173
  continue
174
+ clusters: list[Cluster] = [
175
+ cluster
176
+ for ocm_cluster in ocm_clusters
177
+ if (
178
+ cluster := self._parse_ocm_data_to_cluster(
179
+ ocm_cluster=ocm_cluster
180
+ )
181
+ )
182
+ ]
145
183
  filtered_clusters = self._filter_clusters(
146
184
  clusters=clusters,
147
185
  token_spec_by_name=dependencies.token_spec_by_name,
@@ -157,79 +195,80 @@ class DynatraceTokenProviderIntegration(QontractReconcileIntegration[NoParams]):
157
195
  len(clusters),
158
196
  )
159
197
  for cluster in filtered_clusters:
160
- try:
161
- with DTPOrganizationErrorRate(
162
- integration=self.name,
163
- ocm_env=ocm_env_name,
164
- org_id=cluster.organization_id,
165
- ):
166
- tenant_id = cluster.dt_tenant
167
- if not tenant_id:
168
- _expose_errors_as_service_log(
169
- ocm_client,
170
- cluster_uuid=cluster.external_id,
171
- error=f"Missing label {DTP_TENANT_V2_LABEL}",
172
- )
173
- logging.warn(
174
- f"[{cluster.id=}] Missing value for label {DTP_TENANT_V2_LABEL}"
175
- )
176
- continue
177
- if (
178
- tenant_id
179
- not in dependencies.dynatrace_client_by_tenant_id
198
+ for token_binding in cluster.dt_token_bindings:
199
+ try:
200
+ with DTPOrganizationErrorRate(
201
+ integration=self.name,
202
+ ocm_env=ocm_env_name,
203
+ org_id=cluster.organization_id,
180
204
  ):
181
- _expose_errors_as_service_log(
182
- ocm_client,
183
- cluster_uuid=cluster.external_id,
184
- error=f"Dynatrace tenant {tenant_id} does not exist",
185
- )
186
- logging.warn(
187
- f"[{cluster.id=}] Dynatrace {tenant_id=} does not exist"
188
- )
189
- continue
190
- dt_client = dependencies.dynatrace_client_by_tenant_id[
191
- tenant_id
192
- ]
205
+ tenant_id = token_binding.tenant_id
206
+ if not tenant_id:
207
+ _expose_errors_as_service_log(
208
+ ocm_client,
209
+ cluster_uuid=cluster.external_id,
210
+ error=f"Missing label {DTP_TENANT_V2_LABEL}",
211
+ )
212
+ logging.warning(
213
+ f"[{cluster.id=}] Missing value for label {DTP_TENANT_V2_LABEL}"
214
+ )
215
+ continue
216
+ if (
217
+ tenant_id
218
+ not in dependencies.dynatrace_client_by_tenant_id
219
+ ):
220
+ _expose_errors_as_service_log(
221
+ ocm_client,
222
+ cluster_uuid=cluster.external_id,
223
+ error=f"Dynatrace tenant {tenant_id} does not exist",
224
+ )
225
+ logging.warning(
226
+ f"[{cluster.id=}] Dynatrace {tenant_id=} does not exist"
227
+ )
228
+ continue
229
+ dt_client = dependencies.dynatrace_client_by_tenant_id[
230
+ tenant_id
231
+ ]
193
232
 
194
- token_spec = dependencies.token_spec_by_name.get(
195
- cluster.token_spec_name
196
- )
197
- if not token_spec:
198
- _expose_errors_as_service_log(
199
- ocm_client,
200
- cluster_uuid=cluster.external_id,
201
- error=f"Token spec {cluster.token_spec_name} does not exist",
202
- )
203
- logging.warn(
204
- f"[{cluster.id=}] Token spec '{cluster.token_spec_name}' does not exist"
233
+ token_spec = dependencies.token_spec_by_name.get(
234
+ token_binding.spec_name
205
235
  )
206
- continue
207
- if tenant_id not in existing_dtp_tokens:
208
- existing_dtp_tokens[tenant_id] = (
209
- dt_client.get_token_ids_map_for_name_prefix(
210
- prefix="dtp"
236
+ if not token_spec:
237
+ _expose_errors_as_service_log(
238
+ ocm_client,
239
+ cluster_uuid=cluster.external_id,
240
+ error=f"Token spec {token_binding.spec_name} does not exist",
241
+ )
242
+ logging.warning(
243
+ f"[{cluster.id=}] Token spec '{token_binding.spec_name}' does not exist"
244
+ )
245
+ continue
246
+ if tenant_id not in existing_dtp_tokens:
247
+ existing_dtp_tokens[tenant_id] = (
248
+ dt_client.get_token_ids_map_for_name_prefix(
249
+ prefix="dtp"
250
+ )
211
251
  )
212
- )
213
252
 
214
- """
215
- Note, that we consciously do not parallelize cluster processing
216
- for now. We want to keep stress on OCM at a minimum. The amount
217
- of tagged clusters is currently feasible to be processed sequentially.
218
- """
219
- self.process_cluster(
220
- dry_run=dry_run,
221
- cluster=cluster,
222
- dt_client=dt_client,
223
- ocm_client=ocm_client,
224
- existing_dtp_tokens=existing_dtp_tokens[tenant_id],
225
- tenant_id=tenant_id,
226
- token_spec=token_spec,
227
- ocm_env_name=ocm_env_name,
253
+ """
254
+ Note, that we consciously do not parallelize cluster processing
255
+ for now. We want to keep stress on OCM at a minimum. The amount
256
+ of tagged clusters is currently feasible to be processed sequentially.
257
+ """
258
+ self.process_cluster(
259
+ dry_run=dry_run,
260
+ cluster=cluster,
261
+ dt_client=dt_client,
262
+ ocm_client=ocm_client,
263
+ existing_dtp_tokens=existing_dtp_tokens[tenant_id],
264
+ tenant_id=tenant_id,
265
+ token_spec=token_spec,
266
+ ocm_env_name=ocm_env_name,
267
+ )
268
+ except Exception as e:
269
+ unhandled_exceptions.append(
270
+ f"{ocm_env_name}/{cluster.organization_id}/{cluster.id}: {e}"
228
271
  )
229
- except Exception as e:
230
- unhandled_exceptions.append(
231
- f"{ocm_env_name}/{cluster.organization_id}/{cluster.id}: {e}"
232
- )
233
272
  self._expose_token_metrics()
234
273
 
235
274
  if unhandled_exceptions:
@@ -12,3 +12,16 @@ class K8sSecret(BaseModel):
12
12
  namespace_name: str
13
13
  secret_name: str
14
14
  tokens: list[DynatraceAPIToken]
15
+
16
+
17
+ class TokenSpecTenantBinding(BaseModel):
18
+ spec_name: str
19
+ tenant_id: str
20
+
21
+
22
+ class Cluster(BaseModel):
23
+ id: str
24
+ external_id: str
25
+ organization_id: str
26
+ is_hcp: bool
27
+ dt_token_bindings: list[TokenSpecTenantBinding]
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import logging
4
3
  from collections.abc import Mapping
5
4
  from datetime import timedelta
6
5
  from typing import Any
@@ -21,7 +20,6 @@ from reconcile.utils.ocm.manifests import (
21
20
  patch_manifest,
22
21
  )
23
22
  from reconcile.utils.ocm.service_log import create_service_log
24
- from reconcile.utils.ocm.sre_capability_labels import sre_capability_label_key
25
23
  from reconcile.utils.ocm.syncsets import (
26
24
  create_syncset,
27
25
  get_syncset,
@@ -35,35 +33,25 @@ from reconcile.utils.ocm_base_client import (
35
33
  Thin abstractions of reconcile.ocm module to reduce coupling.
36
34
  """
37
35
 
38
- DTP_LABEL_SEARCH = sre_capability_label_key("dtp", "%")
39
- DTP_TENANT_V2_LABEL = sre_capability_label_key("dtp.v2", "tenant")
40
- DTP_SPEC_V2_LABEL = sre_capability_label_key("dtp.v2", "token-spec")
41
36
 
42
-
43
- class Cluster(BaseModel):
37
+ class OCMCluster(BaseModel):
44
38
  id: str
45
39
  external_id: str
46
40
  organization_id: str
47
- dt_tenant: str
48
- token_spec_name: str
41
+ subscription_id: str
49
42
  is_hcp: bool
43
+ labels: Mapping[str, str]
50
44
 
51
45
  @staticmethod
52
- def from_cluster_details(cluster: ClusterDetails) -> Cluster | None:
53
- dt_tenant = cluster.labels.get_label_value(DTP_TENANT_V2_LABEL)
54
- token_spec_name = cluster.labels.get_label_value(DTP_SPEC_V2_LABEL)
55
- if not dt_tenant or not token_spec_name:
56
- logging.warning(
57
- f"[Missing DTP labels] {cluster.ocm_cluster.id=} {cluster.ocm_cluster.subscription.id=} {dt_tenant=} {token_spec_name=}"
58
- )
59
- return None
60
- return Cluster(
46
+ def from_cluster_details(cluster: ClusterDetails) -> OCMCluster | None:
47
+ labels = {key: label.value for key, label in cluster.labels.labels.items()}
48
+ return OCMCluster(
61
49
  id=cluster.ocm_cluster.id,
62
50
  external_id=cluster.ocm_cluster.external_id,
63
51
  organization_id=cluster.organization_id,
64
- dt_tenant=dt_tenant,
65
- token_spec_name=token_spec_name,
52
+ subscription_id=cluster.ocm_cluster.subscription.id,
66
53
  is_hcp=cluster.ocm_cluster.is_rosa_hypershift(),
54
+ labels=labels,
67
55
  )
68
56
 
69
57
 
@@ -117,13 +105,13 @@ class OCMClient:
117
105
  manifest_map=manifest_map,
118
106
  )
119
107
 
120
- def discover_clusters_by_labels(self, label_filter: Filter) -> list[Cluster]:
108
+ def discover_clusters_by_labels(self, label_filter: Filter) -> list[OCMCluster]:
121
109
  return [
122
110
  cluster
123
111
  for ocm_cluster in discover_clusters_by_labels(
124
112
  ocm_api=self._ocm_client, label_filter=label_filter
125
113
  )
126
- if (cluster := Cluster.from_cluster_details(ocm_cluster))
114
+ if (cluster := OCMCluster.from_cluster_details(ocm_cluster))
127
115
  ]
128
116
 
129
117
  def create_service_log(