qontract-reconcile 0.10.2.dev155__py3-none-any.whl → 0.10.2.dev157__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.dev157
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
@@ -89,7 +89,7 @@ reconcile/openshift_upgrade_watcher.py,sha256=9IB321hlRZZhzdaR9G3zoWAhVv0-KzNiEq
89
89
  reconcile/openshift_users.py,sha256=JUWLb13USlQ4KvXZVsi3JES4csZnXlH0plhxskg_p6A,5300
90
90
  reconcile/openshift_vault_secrets.py,sha256=9rTqV6wzCQx2Oh712E_Xj8wMG7u8Oh-pY8DWjlv4mZw,1660
91
91
  reconcile/quay_base.py,sha256=h5xNjb7EZm8L2JgpO42r6w0UA4im5dabZXJSIW69zKU,1987
92
- reconcile/quay_membership.py,sha256=YDLY7ayDsatEpYCe_iMdf3pkvrbmN8mAwrDKUJUa4Lg,6260
92
+ reconcile/quay_membership.py,sha256=cmeoRdr3-wVlymNHVhzhW0W-Tq6qt1hd2OOIhGXsmrY,6398
93
93
  reconcile/quay_mirror.py,sha256=0KtQFwrvMNtlsPJ9F_-ICaVIjgIUjFxqipvAPcvyg3Q,15338
94
94
  reconcile/quay_mirror_org.py,sha256=tXKuF6JtmaNRwu8_g_65U_Vpd6sFBYeXmJA-flVhylE,10764
95
95
  reconcile/quay_permissions.py,sha256=9KOutS1w4RFQqkvMSy54VtsKNx56-phzP6yI_rEW-B8,4244
@@ -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
@@ -227,7 +227,7 @@ reconcile/glitchtip_project_alerts/integration.py,sha256=BgMx-NyV9mTuv7Sotb2OioC
227
227
  reconcile/glitchtip_project_dsn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
228
228
  reconcile/glitchtip_project_dsn/integration.py,sha256=2iugub-kHYkHNK33n0v9_TeWonuxCPah_VkoTPvaajE,8077
229
229
  reconcile/gql_definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
230
- reconcile/gql_definitions/introspection.json,sha256=8tA2_ZuvhmWLNRV1-JvZeVTdyUDYAVvdSe0mvxm_Uag,2286927
230
+ reconcile/gql_definitions/introspection.json,sha256=cO8ytMUFP2x_mD8oq0pkcrgSFSj7epYDcdQTJCGTw-I,2289286
231
231
  reconcile/gql_definitions/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
232
232
  reconcile/gql_definitions/acs/acs_instances.py,sha256=L91WW9LbhJbBSrECqShQpFtjoBOsmNIYLRpMbx1io5o,2181
233
233
  reconcile/gql_definitions/acs/acs_policies.py,sha256=bN5i4mks10Z23KJSj7jqp966Osq2dps4d-sPH9gjxEA,7008
@@ -392,7 +392,7 @@ reconcile/gql_definitions/openshift_groups/managed_roles.py,sha256=rsHMgDWwnh0RR
392
392
  reconcile/gql_definitions/openshift_serviceaccount_tokens/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
393
393
  reconcile/gql_definitions/openshift_serviceaccount_tokens/tokens.py,sha256=FeraeQThHl2UbhTuCPDwm3ltczF_jfZn5E3Squ8-znw,3313
394
394
  reconcile/gql_definitions/quay_membership/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
395
- reconcile/gql_definitions/quay_membership/quay_membership.py,sha256=H2xHvdNr3K0QzB2dituwStUIWCqePt35dkgeUZycECM,2824
395
+ reconcile/gql_definitions/quay_membership/quay_membership.py,sha256=MKBkrE-1YYelaAAxOdpqUwCo45kOVC8q29vXArqK_zM,3075
396
396
  reconcile/gql_definitions/rhidp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
397
397
  reconcile/gql_definitions/rhidp/organizations.py,sha256=dW9y3ewFu3E-DFrZAi_SEewHYR0MWYeOB52vwnVcq5E,2580
398
398
  reconcile/gql_definitions/service_dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -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.dev157.dist-info/METADATA,sha256=a0v4VwAX4CUOblutkjBWxC1LUvx_HwYZUsCE6exNgto,24627
801
+ qontract_reconcile-0.10.2.dev157.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
802
+ qontract_reconcile-0.10.2.dev157.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
803
+ qontract_reconcile-0.10.2.dev157.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(
@@ -11541,6 +11541,30 @@
11541
11541
  },
11542
11542
  "isDeprecated": false,
11543
11543
  "deprecationReason": null
11544
+ },
11545
+ {
11546
+ "name": "external_users",
11547
+ "description": null,
11548
+ "args": [],
11549
+ "type": {
11550
+ "kind": "NON_NULL",
11551
+ "name": null,
11552
+ "ofType": {
11553
+ "kind": "LIST",
11554
+ "name": null,
11555
+ "ofType": {
11556
+ "kind": "NON_NULL",
11557
+ "name": null,
11558
+ "ofType": {
11559
+ "kind": "OBJECT",
11560
+ "name": "ExternalUser_v1",
11561
+ "ofType": null
11562
+ }
11563
+ }
11564
+ }
11565
+ },
11566
+ "isDeprecated": false,
11567
+ "deprecationReason": null
11544
11568
  }
11545
11569
  ],
11546
11570
  "inputFields": null,
@@ -27145,13 +27169,21 @@
27145
27169
  "description": null,
27146
27170
  "args": [],
27147
27171
  "type": {
27148
- "kind": "NON_NULL",
27149
- "name": null,
27150
- "ofType": {
27151
- "kind": "SCALAR",
27152
- "name": "String",
27153
- "ofType": null
27154
- }
27172
+ "kind": "SCALAR",
27173
+ "name": "String",
27174
+ "ofType": null
27175
+ },
27176
+ "isDeprecated": false,
27177
+ "deprecationReason": null
27178
+ },
27179
+ {
27180
+ "name": "quay_username",
27181
+ "description": null,
27182
+ "args": [],
27183
+ "type": {
27184
+ "kind": "SCALAR",
27185
+ "name": "String",
27186
+ "ofType": null
27155
27187
  },
27156
27188
  "isDeprecated": false,
27157
27189
  "deprecationReason": null
@@ -27179,6 +27211,26 @@
27179
27211
  },
27180
27212
  "isDeprecated": false,
27181
27213
  "deprecationReason": null
27214
+ },
27215
+ {
27216
+ "name": "roles",
27217
+ "description": null,
27218
+ "args": [],
27219
+ "type": {
27220
+ "kind": "LIST",
27221
+ "name": null,
27222
+ "ofType": {
27223
+ "kind": "NON_NULL",
27224
+ "name": null,
27225
+ "ofType": {
27226
+ "kind": "OBJECT",
27227
+ "name": "Role_v1",
27228
+ "ofType": null
27229
+ }
27230
+ }
27231
+ },
27232
+ "isDeprecated": false,
27233
+ "deprecationReason": null
27182
27234
  }
27183
27235
  ],
27184
27236
  "inputFields": null,
@@ -37,6 +37,9 @@ query QuayMembership {
37
37
  bots {
38
38
  quay_username
39
39
  }
40
+ external_users {
41
+ quay_username
42
+ }
40
43
  expirationDate
41
44
  }
42
45
  }
@@ -72,9 +75,14 @@ class BotV1(ConfiguredBaseModel):
72
75
  quay_username: Optional[str] = Field(..., alias="quay_username")
73
76
 
74
77
 
78
+ class ExternalUserV1(ConfiguredBaseModel):
79
+ quay_username: Optional[str] = Field(..., alias="quay_username")
80
+
81
+
75
82
  class RoleV1(ConfiguredBaseModel):
76
83
  users: list[UserV1] = Field(..., alias="users")
77
84
  bots: list[BotV1] = Field(..., alias="bots")
85
+ external_users: list[ExternalUserV1] = Field(..., alias="external_users")
78
86
  expiration_date: Optional[str] = Field(..., alias="expirationDate")
79
87
 
80
88
 
@@ -5,6 +5,7 @@ from collections.abc import Sequence
5
5
  from reconcile.gql_definitions.quay_membership import quay_membership
6
6
  from reconcile.gql_definitions.quay_membership.quay_membership import (
7
7
  BotV1,
8
+ ExternalUserV1,
8
9
  PermissionQuayOrgTeamV1,
9
10
  UserV1,
10
11
  )
@@ -80,7 +81,7 @@ def fetch_current_state(quay_api_store):
80
81
  return state
81
82
 
82
83
 
83
- def get_usernames(users: Sequence[UserV1 | BotV1]) -> list[str]:
84
+ def get_usernames(users: Sequence[UserV1 | BotV1 | ExternalUserV1]) -> list[str]:
84
85
  return [u.quay_username for u in users if u.quay_username]
85
86
 
86
87
 
@@ -92,7 +93,11 @@ def fetch_desired_state():
92
93
  p = process_permission(permission)
93
94
  members: list[str] = []
94
95
  for role in expiration.filter(permission.roles):
95
- members += get_usernames(role.users) + get_usernames(role.bots)
96
+ members += (
97
+ get_usernames(role.users)
98
+ + get_usernames(role.bots)
99
+ + get_usernames(role.external_users)
100
+ )
96
101
 
97
102
  state.add(p, members)
98
103