qontract-reconcile 0.10.2.dev167__py3-none-any.whl → 0.10.2.dev169__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.dev167
3
+ Version: 0.10.2.dev169
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
@@ -17,12 +17,12 @@ reconcile/dashdotdb_base.py,sha256=83ZWIf5JJk3P_D69y2TmXRcQr6ELJGlv10OM0h7fJVs,4
17
17
  reconcile/dashdotdb_cso.py,sha256=QRK0YfIqO4rehs8btD3l_GXIO2ZIycTQEKEthBdB0xA,3639
18
18
  reconcile/dashdotdb_dora.py,sha256=olQnGp4JYpoh1lQEf9kHc2y3bMaAIUXEB6eFohWH8Io,17859
19
19
  reconcile/dashdotdb_dvo.py,sha256=lCkZ0iby6HrNQb-3kYb6xrt8wCjVUZYxKzz9SiStfHU,8946
20
- reconcile/dashdotdb_slo.py,sha256=PU1GzT6Uy07IIO3Y62cFfRfaBJYUPrMkMp71Up80_bg,8334
20
+ reconcile/dashdotdb_slo.py,sha256=TvKdMOtUZcZP9QydcUJMKh0zURHgOMN_RTpQpCkD1Z8,3960
21
21
  reconcile/database_access_manager.py,sha256=Z3aAmw2LsmMIIor-bOGzziVZdVNC82Gmw8oHBUAFf-8,25577
22
22
  reconcile/deadmanssnitch.py,sha256=n-5W-djUgwzpmdDM4eQIZpkkDmHY0vndt-42LJXI4Y8,7491
23
23
  reconcile/email_sender.py,sha256=38Wvl6WHqCwlqLx4oxVJOIeDmoJsyitD3g1F4jTkAj8,4246
24
24
  reconcile/gabi_authorized_users.py,sha256=Jwvo97nzUX3NIl2VHKuZlT0-I40qk2VnACbafe91T2o,4854
25
- reconcile/gcp_image_mirror.py,sha256=M5pimd0j13BBWCa8vX0fftWO0pHBfQCATIIpOGggDSA,10332
25
+ reconcile/gcp_image_mirror.py,sha256=1ThuUff_04ZdF6uxcLoDuHhoNA3OIw0V-z0-CwdPE2w,9538
26
26
  reconcile/github_org.py,sha256=Wc5cZamatuWsW2ZJT2ib5ps8l3iY3RXHwNUxVJerqz0,14173
27
27
  reconcile/github_owners.py,sha256=viE1KJ-zaTxuZ5yItg2C263J0brn-Q-3hR_DkYDMbhY,3122
28
28
  reconcile/github_repo_invites.py,sha256=U9UCzNVwrZ7MqODtFah8ogH0NNY-XjBin7G9gqHtCUY,2690
@@ -90,7 +90,7 @@ reconcile/openshift_users.py,sha256=JUWLb13USlQ4KvXZVsi3JES4csZnXlH0plhxskg_p6A,
90
90
  reconcile/openshift_vault_secrets.py,sha256=9rTqV6wzCQx2Oh712E_Xj8wMG7u8Oh-pY8DWjlv4mZw,1660
91
91
  reconcile/quay_base.py,sha256=h5xNjb7EZm8L2JgpO42r6w0UA4im5dabZXJSIW69zKU,1987
92
92
  reconcile/quay_membership.py,sha256=cmeoRdr3-wVlymNHVhzhW0W-Tq6qt1hd2OOIhGXsmrY,6398
93
- reconcile/quay_mirror.py,sha256=0KtQFwrvMNtlsPJ9F_-ICaVIjgIUjFxqipvAPcvyg3Q,15338
93
+ reconcile/quay_mirror.py,sha256=PBooiA0ShZpWYfO6oeKFqYYT6Syi7Q8JJD9kj0wRRLg,14030
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
96
96
  reconcile/quay_repos.py,sha256=cuEYG0HUe0ut5yvLdEwOF5-CmccpXQHRb_wDazvDrvQ,6895
@@ -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=wNGZv8V6ivviCcwPLw34nzTR5fgwKRyE68Z93HbmBBs,2296576
230
+ reconcile/gql_definitions/introspection.json,sha256=zD5QWpv9KYduZHr8a6dFoGz-oSHM1ItN1Trn02Lh7XQ,2297479
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
@@ -295,7 +295,7 @@ reconcile/gql_definitions/common/pipeline_providers.py,sha256=9rpsqPuvj82B4ki56x
295
295
  reconcile/gql_definitions/common/quay_instances.py,sha256=toBkdYYVTmEafezAHZKgaW-mQ29xEW6jeronzsAlNyI,1786
296
296
  reconcile/gql_definitions/common/quay_orgs.py,sha256=NhA8kqvVUDbrsryEvEL5mlIv5R3T4XNhSRXtfL_yptY,1788
297
297
  reconcile/gql_definitions/common/reserved_networks.py,sha256=yP9qSQCaSQcva-ZgTnZp09qH27ur5_qK080ToIs04MY,2560
298
- reconcile/gql_definitions/common/saas_files.py,sha256=DA6aJspbuoSkVHlFI4HQ-YjdE5iN3eVg1k2lNtW0840,16863
298
+ reconcile/gql_definitions/common/saas_files.py,sha256=d1L_S5LgCMa4QuAqZGQYTWb5L_nPCOxSEjU4O__OeBU,17728
299
299
  reconcile/gql_definitions/common/saas_target_namespaces.py,sha256=4VYP2VbwY8WVwtSFk2-jsUNhSmRD3X4FWKxetOKvmd0,2835
300
300
  reconcile/gql_definitions/common/saasherder_settings.py,sha256=nqQLcMwYxLseqq0BEcVvmrpIj2eQq0h8XDSpLN6GGCw,1793
301
301
  reconcile/gql_definitions/common/slack_workspaces.py,sha256=2o0kgi4QiaRuNmZJnc_By4F6NsKIdRaXkrufRQw7Nok,1753
@@ -307,7 +307,7 @@ reconcile/gql_definitions/cost_report/app_names.py,sha256=fzqYXyiTSll359J1F1o7qa
307
307
  reconcile/gql_definitions/cost_report/cost_namespaces.py,sha256=URRozAgSa9OnkqOCZf3MGH21_wcnsqYl0n-olXdjQH0,2286
308
308
  reconcile/gql_definitions/cost_report/settings.py,sha256=0nhBDJ5MZ1m7XkNDGrRLmsnUbzqZ4WRh_DDEEzKhcxU,2153
309
309
  reconcile/gql_definitions/dashdotdb_slo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
310
- reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py,sha256=MYYpVOc8Ze9w7k6-tlUkp5OaPG_5bqHPS5FhfWTw00U,4335
310
+ reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py,sha256=a1zLeL_NCbK25fOeT1gZOch8HNPFcHhzVXQty3jKT_s,2430
311
311
  reconcile/gql_definitions/dynatrace_token_provider/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
312
312
  reconcile/gql_definitions/dynatrace_token_provider/dynatrace_bootstrap_tokens.py,sha256=5gTuAnR2rnx2k6Rn7FMEAzw6GCZ6F5HZbqkmJ9-3NI4,2244
313
313
  reconcile/gql_definitions/dynatrace_token_provider/token_specs.py,sha256=XGsMuB8gowRpqJjkD_KRomx-1OswzyWbF4qjVdhionk,2555
@@ -350,6 +350,7 @@ reconcile/gql_definitions/fragments/prometheus_instance.py,sha256=12ltnV9kdEw6Ln
350
350
  reconcile/gql_definitions/fragments/resource_limits_requirements.py,sha256=ucskQ_a8RxvFl5-IWxz5kk3g4-5Pvh_W4N3nLmuKxi0,744
351
351
  reconcile/gql_definitions/fragments/resource_requests_requirements.py,sha256=TFKO4YALFPanSvZvIJFz0dCioBU7i73Q6hkDtGMvs9I,736
352
352
  reconcile/gql_definitions/fragments/resource_values.py,sha256=-N2lNRhWp8PgocmIeX3U9f3l90Q97N2lXoq1pXdb_LE,742
353
+ reconcile/gql_definitions/fragments/saas_slo_document.py,sha256=6Ko_Kqny9gixPLKwr8RHL6DNx32rkNV24myurCVko-Q,2635
353
354
  reconcile/gql_definitions/fragments/saas_target_namespace.py,sha256=6f6WaerElaRi9_Ro-0CyWUkMHsbXlm0h9YXklftBwag,3991
354
355
  reconcile/gql_definitions/fragments/serviceaccount_token.py,sha256=2pG4rxAjvT-YsFBnm4zl301i7DCYznp99HOEGA-216I,1117
355
356
  reconcile/gql_definitions/fragments/terraform_state.py,sha256=S5QuTR9YlvUObiU7hevS9ybxZEssWoRGqCR9YtGwePs,1024
@@ -650,6 +651,7 @@ reconcile/utils/prometheus.py,sha256=Ad0rwLbxRuuYjHwkwJloHEdK0bvy42h-p-HIT1DhDhs
650
651
  reconcile/utils/promotion_state.py,sha256=McSgGj3oog83ThJCrMR2v8q6Xb_Pxij-HEe_RbDu8cg,3946
651
652
  reconcile/utils/promtool.py,sha256=xmPBWEApkk0L2qZBAvTxakNXxfTz-tVLPFxGnpsxXnM,2831
652
653
  reconcile/utils/quay_api.py,sha256=uE_jxcdy3ViHtYFAfwDQuFDaO7Pr6AAPoVnmORbyHio,7822
654
+ reconcile/utils/quay_mirror.py,sha256=dpWCNv5lITwIk6Q9RkmqaQKHNk_JPy27UQEribJ7E-U,1324
653
655
  reconcile/utils/raw_github_api.py,sha256=2WKtE8ABYYB9UGOAh9N_kLkksBWL3320Z2_scteZddI,2805
654
656
  reconcile/utils/repo_owners.py,sha256=BHrAXxKyvn4qWJwFPWYGTtfgnLmYnWtYFEJGFeD__FE,6573
655
657
  reconcile/utils/rest_api_base.py,sha256=MT7tp6CQO2S5aKfVOzw_hipWg7wAGoOqkm4qurI1hEU,4342
@@ -658,6 +660,7 @@ reconcile/utils/secret_reader.py,sha256=MaP56KZaAE35EyYbgAitdm6fUSxdzWeGFSOym9qi
658
660
  reconcile/utils/semver_helper.py,sha256=-WfPOMSA2v1h7hT3PwVf-Htg7wOsoKlQC1JdmDX2Ars,1268
659
661
  reconcile/utils/sharding.py,sha256=DDBHfs5TT9UgjmzewiXUjbncnrPuceAZWeOA4veGa7s,843
660
662
  reconcile/utils/slack_api.py,sha256=iaOFzv3wiZRhcgYK2NB4lsG6ymNsGk2MEuj0PgZVp7w,17355
663
+ reconcile/utils/slo_document_manager.py,sha256=CPgM2oH4AVzBqenakWo59R5yfwB62tnxSnSOHgir7l8,9500
661
664
  reconcile/utils/smtp_client.py,sha256=0xefB4I9E5eBB-FlxFJYjvz3Kvuqi_K3Ma_Wk0NAQKM,2779
662
665
  reconcile/utils/sqs_gateway.py,sha256=XNIf3PY4UCPNufP2Ul0UJj3fKlt5larBba-VTT-41Fg,2265
663
666
  reconcile/utils/state.py,sha256=az4tBmZ0EdbFcAGiBVUxs3cr2-BVWsuDQiNTvjjQq8s,16378
@@ -804,7 +807,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
804
807
  tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
805
808
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
806
809
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
807
- qontract_reconcile-0.10.2.dev167.dist-info/METADATA,sha256=gC7vJiajfAcAoktNwpvYbj0FZ0-Lje0pXtytx7-S-Tc,24627
808
- qontract_reconcile-0.10.2.dev167.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
809
- qontract_reconcile-0.10.2.dev167.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
810
- qontract_reconcile-0.10.2.dev167.dist-info/RECORD,,
810
+ qontract_reconcile-0.10.2.dev169.dist-info/METADATA,sha256=4jU4ZsBrELF7np1OLiyWn5SGED63Y6QaiZhwOVGyvh4,24627
811
+ qontract_reconcile-0.10.2.dev169.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
812
+ qontract_reconcile-0.10.2.dev169.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
813
+ qontract_reconcile-0.10.2.dev169.dist-info/RECORD,,
@@ -1,11 +1,6 @@
1
- from collections.abc import Iterable
2
- from dataclasses import dataclass
3
- from math import isnan
4
1
  from typing import Any
5
2
 
6
- import jinja2
7
3
  import requests
8
- from requests import Response
9
4
  from sretoolbox.utils import threaded
10
5
 
11
6
  from reconcile.dashdotdb_base import (
@@ -13,9 +8,9 @@ from reconcile.dashdotdb_base import (
13
8
  DashdotdbBase,
14
9
  )
15
10
  from reconcile.gql_definitions.dashdotdb_slo.slo_documents_query import (
16
- SLODocumentV1,
17
11
  query,
18
12
  )
13
+ from reconcile.gql_definitions.fragments.saas_slo_document import SLODocument
19
14
  from reconcile.typed_queries.app_interface_vault_settings import (
20
15
  get_app_interface_vault_settings,
21
16
  )
@@ -24,40 +19,22 @@ from reconcile.utils.secret_reader import (
24
19
  SecretReaderBase,
25
20
  create_secret_reader,
26
21
  )
22
+ from reconcile.utils.slo_document_manager import (
23
+ SLODetails,
24
+ SLODocumentManager,
25
+ )
27
26
 
28
27
  QONTRACT_INTEGRATION = "dashdotdb-slo"
28
+ READ_TIMEOUT = 300
29
+ MAX_RETRIES = 2
29
30
 
30
31
 
31
- def get_slo_documents() -> list[SLODocumentV1]:
32
+ def get_slo_documents() -> list[SLODocument]:
32
33
  gqlapi = gql.get_api()
33
34
  data = query(gqlapi.query)
34
35
  return list(data.slo_documents or [])
35
36
 
36
37
 
37
- @dataclass
38
- class ServiceSLO:
39
- name: str
40
- sli_type: str
41
- slo_doc_name: str
42
- namespace_name: str
43
- cluster_name: str
44
- service_name: str
45
- value: float
46
- target: float
47
-
48
- def dashdot_payload(self) -> dict[str, Any]:
49
- return {
50
- "name": self.name,
51
- "SLIType": self.sli_type,
52
- "SLODoc": {"name": self.slo_doc_name},
53
- "namespace": {"name": self.namespace_name},
54
- "cluster": {"name": self.cluster_name},
55
- "service": {"name": self.service_name},
56
- "value": self.value,
57
- "target": self.target,
58
- }
59
-
60
-
61
38
  class DashdotdbSLO(DashdotdbBase):
62
39
  def __init__(
63
40
  self, dry_run: bool, thread_pool_size: int, secret_reader: SecretReaderBase
@@ -70,19 +47,28 @@ class DashdotdbSLO(DashdotdbBase):
70
47
  secret_reader=secret_reader,
71
48
  )
72
49
 
73
- def _post(self, service_slos: Iterable[ServiceSLO]) -> Response | None:
74
- for item in service_slos:
75
- LOG.debug(f"About to POST SLO JSON item to dashdotDB:\n{item}\n")
76
-
77
- response = None
78
-
79
- for item in service_slos:
80
- slo_name = item.name
81
- endpoint = f"{self.dashdotdb_url}/api/v1/serviceslometrics/{slo_name}"
82
- payload = item.dashdot_payload()
83
- if self.dry_run:
84
- continue
50
+ @staticmethod
51
+ def get_dash_dot_db_payload(slo: SLODetails) -> dict[str, Any]:
52
+ return {
53
+ "name": slo.slo.name,
54
+ "SLIType": slo.slo.sli_type,
55
+ "SLODoc": {"name": slo.slo_document_name},
56
+ "namespace": {"name": slo.namespace_name},
57
+ "cluster": {"name": slo.cluster_name},
58
+ "service": {"name": slo.service_name},
59
+ "value": slo.current_slo_value,
60
+ "target": slo.slo.slo_target,
61
+ }
85
62
 
63
+ def _post(self, service_slo: SLODetails) -> None:
64
+ LOG.debug(f"About to POST SLO JSON item to dashdotDB:\n{service_slo}\n")
65
+ slo_name = service_slo.slo.name
66
+ endpoint = f"{self.dashdotdb_url}/api/v1/serviceslometrics/{slo_name}"
67
+ if service_slo.slo.slo_target_unit == "percent_0_1":
68
+ service_slo.current_slo_value *= 100
69
+ service_slo.slo.slo_target *= 100
70
+ payload = self.get_dash_dot_db_payload(service_slo)
71
+ if not self.dry_run:
86
72
  LOG.info("%s syncing slo %s", self.logmarker, slo_name)
87
73
  try:
88
74
  response = self._do_post(endpoint, payload)
@@ -94,127 +80,30 @@ class DashdotdbSLO(DashdotdbBase):
94
80
  LOG.error("%s error posting %s - %s", self.logmarker, slo_name, details)
95
81
 
96
82
  LOG.info("%s slo %s synced", self.logmarker, slo_name)
97
- return response
98
-
99
- def _get_service_slo(self, slo_document: SLODocumentV1) -> list[ServiceSLO]:
100
- LOG.debug("SLO: processing %s", slo_document.name)
101
- result: list[ServiceSLO] = []
102
- for namespace_access in slo_document.namespaces:
103
- if (
104
- namespace_access.slo_namespace
105
- and namespace_access.prometheus_access is None
106
- ):
107
- continue
108
-
109
- ns = namespace_access.namespace
110
- promtoken: str | None = None
111
- username: str | None = None
112
- password: str | None = None
113
- if namespace_access.prometheus_access:
114
- promurl = namespace_access.prometheus_access.url
115
- if (
116
- namespace_access.prometheus_access.username
117
- and namespace_access.prometheus_access.password
118
- ):
119
- username = self.secret_reader.read_secret(
120
- namespace_access.prometheus_access.username
121
- )
122
- password = self.secret_reader.read_secret(
123
- namespace_access.prometheus_access.password
124
- )
125
- else:
126
- promurl = ns.cluster.prometheus_url
127
- if not ns.cluster.automation_token:
128
- LOG.error(
129
- "namespace does not have automation token set %s - skipping", ns
130
- )
131
- continue
132
- promtoken = self._get_automation_token(ns.cluster.automation_token)
133
- for slo in slo_document.slos or []:
134
- unit = slo.slo_target_unit
135
- expr = slo.expr
136
- template = jinja2.Template(expr)
137
- window = slo.slo_parameters.window
138
- promquery = template.render({"window": window})
139
-
140
- try:
141
- prom_response = self._promget(
142
- url=promurl,
143
- params={"query": (f"{promquery}")},
144
- token=promtoken,
145
- username=username,
146
- password=password,
147
- )
148
- except requests.exceptions.ConnectionError as error:
149
- # This can happen when prometheus is unreachable, or when running locally
150
- # and some prometheus URL are openshift service names. The trick is to run
151
- # with `oc port-forward` and update the local hosts file if we need to query those.
152
- LOG.error(
153
- f"{self.logmarker} Could not reach prometheus at {promurl}: {error}."
154
- f"Skipping SLOs from SLO doc {slo_document.name}"
155
- )
156
- # cannot connect to this prometheus, skip all
157
- raise
158
- except requests.exceptions.HTTPError as error:
159
- LOG.error(
160
- f"{self.logmarker} Error wile querying {promurl}: {error}."
161
- f"Skipping SLO '{slo.name} from SLO doc {slo_document.name}"
162
- )
163
- # it could be a query issue, keep processing other SLOs from this doc
164
- continue
165
-
166
- prom_result = prom_response["data"]["result"]
167
- if not prom_result:
168
- continue
169
-
170
- slo_value = prom_result[0]["value"]
171
- if not slo_value:
172
- continue
173
-
174
- slo_value = float(slo_value[1])
175
- if isnan(slo_value):
176
- LOG.warning(
177
- f"{self.logmarker} Skipping SLO '{slo.name}' in SLO doc '{slo_document.name}'"
178
- "as the obtained value is not a number (maybe a division by 0?)"
179
- )
180
- continue
181
- slo_target = float(slo.slo_target)
182
-
183
- # In Dash.DB we want to always store SLOs in percentages
184
- if unit == "percent_0_1":
185
- slo_value *= 100
186
- slo_target *= 100
187
-
188
- result.append(
189
- ServiceSLO(
190
- name=slo.name,
191
- sli_type=slo.sli_type,
192
- namespace_name=ns.name,
193
- cluster_name=ns.cluster.name,
194
- service_name=ns.app.name,
195
- value=slo_value,
196
- target=slo_target,
197
- slo_doc_name=slo_document.name,
198
- )
199
- )
200
- return result
201
83
 
202
84
  def run(self) -> None:
203
85
  slo_documents = get_slo_documents()
204
86
 
205
- service_slos: list[list[ServiceSLO]] = threaded.run(
206
- func=self._get_service_slo,
207
- iterable=slo_documents,
87
+ slo_document_manager = SLODocumentManager(
88
+ slo_documents=slo_documents,
89
+ secret_reader=self.secret_reader,
208
90
  thread_pool_size=self.thread_pool_size,
91
+ read_timeout=READ_TIMEOUT,
92
+ max_retries=MAX_RETRIES,
209
93
  )
210
94
 
95
+ slo_details_list = slo_document_manager.get_current_slo_list()
96
+ valid_slo_list = [slo for slo in slo_details_list if slo]
97
+
211
98
  self._get_token()
212
- threaded.run(
213
- func=self._post,
214
- iterable=service_slos,
215
- thread_pool_size=self.thread_pool_size,
216
- )
217
- self._close_token()
99
+ try:
100
+ threaded.run(
101
+ func=self._post,
102
+ iterable=valid_slo_list,
103
+ thread_pool_size=self.thread_pool_size,
104
+ )
105
+ finally:
106
+ self._close_token()
218
107
 
219
108
 
220
109
  def run(dry_run: bool = False, thread_pool_size: int = 10) -> None:
@@ -1,7 +1,6 @@
1
1
  import base64
2
2
  import logging
3
3
  import os
4
- import re
5
4
  import tempfile
6
5
  import time
7
6
  from typing import Any, Self
@@ -23,6 +22,7 @@ from reconcile.gql_definitions.fragments.container_image_mirror import (
23
22
  )
24
23
  from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
25
24
  from reconcile.utils import gql
25
+ from reconcile.utils.quay_mirror import record_timestamp, sync_tag
26
26
  from reconcile.utils.secret_reader import SecretReader
27
27
 
28
28
  QONTRACT_INTEGRATION = "gcp-image-mirror"
@@ -112,25 +112,6 @@ class QuayMirror:
112
112
 
113
113
  return summary
114
114
 
115
- @staticmethod
116
- def sync_tag(
117
- tags: list[str] | None, tags_exclude: list[str] | None, candidate: str
118
- ) -> bool:
119
- if tags is not None:
120
- # When tags is defined, we don't look at tags_exclude
121
- return any(re.match(tag, candidate) for tag in tags)
122
-
123
- if tags_exclude is not None:
124
- return any(re.match(tag, candidate) for tag in tags_exclude)
125
- for tag_exclude in tags_exclude:
126
- if re.match(tag_exclude, candidate):
127
- return False
128
- return True
129
-
130
- # Both tags and tags_exclude are None, so
131
- # tag must be synced
132
- return True
133
-
134
115
  # second layer of processing that matches up pull/push creds with each repo and determines what tags need to be synced
135
116
  def process_sync_tasks(self, repos_to_sync: list[ImageSyncItem]) -> list[SyncTask]:
136
117
  eight_hours = 28800 # 60 * 60 * 8
@@ -165,7 +146,7 @@ class QuayMirror:
165
146
  )
166
147
 
167
148
  for tag in image_mirror:
168
- if not self.sync_tag(
149
+ if not sync_tag(
169
150
  tags=item.mirror.tags,
170
151
  tags_exclude=item.mirror.tags_exclude,
171
152
  candidate=tag,
@@ -234,12 +215,12 @@ class QuayMirror:
234
215
  with open(control_file_path, encoding="locale") as file_obj:
235
216
  last_deep_sync = float(file_obj.read())
236
217
  except FileNotFoundError:
237
- self._record_timestamp(control_file_path)
218
+ record_timestamp(control_file_path)
238
219
  return True
239
220
 
240
221
  next_deep_sync = last_deep_sync + interval
241
222
  if time.time() >= next_deep_sync:
242
- self._record_timestamp(control_file_path)
223
+ record_timestamp(control_file_path)
243
224
  return True
244
225
 
245
226
  return False
@@ -249,11 +230,6 @@ class QuayMirror:
249
230
  token = base64.b64decode(raw_data["token"]).decode()
250
231
  return f"{raw_data['user']}:{token}"
251
232
 
252
- @staticmethod
253
- def _record_timestamp(path: str) -> None:
254
- with open(path, "w", encoding="locale") as file_object:
255
- file_object.write(str(time.time()))
256
-
257
233
  def _get_push_creds(self) -> dict[str, str]:
258
234
  result = gql_gcp_projects.query(query_func=self.gqlapi.query)
259
235
 
@@ -18,6 +18,7 @@ from pydantic import ( # noqa: F401 # pylint: disable=W0611
18
18
  )
19
19
 
20
20
  from reconcile.gql_definitions.fragments.oc_connection_cluster import OcConnectionCluster
21
+ from reconcile.gql_definitions.fragments.saas_slo_document import SLODocument
21
22
  from reconcile.gql_definitions.fragments.saas_target_namespace import SaasTargetNamespace
22
23
  from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
23
24
 
@@ -53,6 +54,50 @@ fragment OcConnectionCluster on Cluster_v1 {
53
54
  }
54
55
  }
55
56
 
57
+ fragment SLODocument on SLODocument_v1 {
58
+ name
59
+ namespaces {
60
+ prometheusAccess {
61
+ url
62
+ username {
63
+ ... VaultSecret
64
+ }
65
+ password {
66
+ ... VaultSecret
67
+ }
68
+ }
69
+ namespace {
70
+ name
71
+ app {
72
+ name
73
+ }
74
+ cluster {
75
+ name
76
+ automationToken {
77
+ ... VaultSecret
78
+ }
79
+ prometheusUrl
80
+ spec {
81
+ private
82
+ }
83
+ }
84
+ }
85
+ SLONamespace {
86
+ name
87
+ }
88
+ }
89
+ slos {
90
+ name
91
+ expr
92
+ SLIType
93
+ SLOParameters {
94
+ window
95
+ }
96
+ SLOTarget
97
+ SLOTargetUnit
98
+ }
99
+ }
100
+
56
101
  fragment SaasTargetNamespace on Namespace_v1 {
57
102
  name
58
103
  labels
@@ -253,6 +298,9 @@ query SaasFiles {
253
298
  namespace {
254
299
  ...SaasTargetNamespace
255
300
  }
301
+ slos {
302
+ ...SLODocument
303
+ }
256
304
  namespaceSelector {
257
305
  jsonPathSelectors {
258
306
  include
@@ -512,6 +560,7 @@ class SaasResourceTemplateTargetV2(ConfiguredBaseModel):
512
560
  path: Optional[str] = Field(..., alias="path")
513
561
  name: Optional[str] = Field(..., alias="name")
514
562
  namespace: Optional[SaasTargetNamespace] = Field(..., alias="namespace")
563
+ slos: Optional[list[SLODocument]] = Field(..., alias="slos")
515
564
  namespace_selector: Optional[SaasResourceTemplateTargetNamespaceSelectorV1] = Field(..., alias="namespaceSelector")
516
565
  provider: Optional[str] = Field(..., alias="provider")
517
566
  ref: str = Field(..., alias="ref")
@@ -17,19 +17,11 @@ from pydantic import ( # noqa: F401 # pylint: disable=W0611
17
17
  Json,
18
18
  )
19
19
 
20
- from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
20
+ from reconcile.gql_definitions.fragments.saas_slo_document import SLODocument
21
21
 
22
22
 
23
23
  DEFINITION = """
24
- fragment VaultSecret on VaultSecret_v1 {
25
- path
26
- field
27
- version
28
- format
29
- }
30
-
31
- query SLODocuments {
32
- slo_documents: slo_document_v1 {
24
+ fragment SLODocument on SLODocument_v1 {
33
25
  name
34
26
  namespaces {
35
27
  prometheusAccess {
@@ -71,6 +63,18 @@ query SLODocuments {
71
63
  SLOTarget
72
64
  SLOTargetUnit
73
65
  }
66
+ }
67
+
68
+ fragment VaultSecret on VaultSecret_v1 {
69
+ path
70
+ field
71
+ version
72
+ format
73
+ }
74
+
75
+ query SLODocuments {
76
+ slo_documents: slo_document_v1 {
77
+ ... SLODocument
74
78
  }
75
79
  }
76
80
  """
@@ -82,64 +86,8 @@ class ConfiguredBaseModel(BaseModel):
82
86
  extra=Extra.forbid
83
87
 
84
88
 
85
- class SLOExternalPrometheusAccessV1(ConfiguredBaseModel):
86
- url: str = Field(..., alias="url")
87
- username: Optional[VaultSecret] = Field(..., alias="username")
88
- password: Optional[VaultSecret] = Field(..., alias="password")
89
-
90
-
91
- class AppV1(ConfiguredBaseModel):
92
- name: str = Field(..., alias="name")
93
-
94
-
95
- class ClusterSpecV1(ConfiguredBaseModel):
96
- private: bool = Field(..., alias="private")
97
-
98
-
99
- class ClusterV1(ConfiguredBaseModel):
100
- name: str = Field(..., alias="name")
101
- automation_token: Optional[VaultSecret] = Field(..., alias="automationToken")
102
- prometheus_url: str = Field(..., alias="prometheusUrl")
103
- spec: Optional[ClusterSpecV1] = Field(..., alias="spec")
104
-
105
-
106
- class NamespaceV1(ConfiguredBaseModel):
107
- name: str = Field(..., alias="name")
108
- app: AppV1 = Field(..., alias="app")
109
- cluster: ClusterV1 = Field(..., alias="cluster")
110
-
111
-
112
- class SLONamespacesV1_NamespaceV1(ConfiguredBaseModel):
113
- name: str = Field(..., alias="name")
114
-
115
-
116
- class SLONamespacesV1(ConfiguredBaseModel):
117
- prometheus_access: Optional[SLOExternalPrometheusAccessV1] = Field(..., alias="prometheusAccess")
118
- namespace: NamespaceV1 = Field(..., alias="namespace")
119
- slo_namespace: Optional[SLONamespacesV1_NamespaceV1] = Field(..., alias="SLONamespace")
120
-
121
-
122
- class SLODocumentSLOSLOParametersV1(ConfiguredBaseModel):
123
- window: str = Field(..., alias="window")
124
-
125
-
126
- class SLODocumentSLOV1(ConfiguredBaseModel):
127
- name: str = Field(..., alias="name")
128
- expr: str = Field(..., alias="expr")
129
- sli_type: str = Field(..., alias="SLIType")
130
- slo_parameters: SLODocumentSLOSLOParametersV1 = Field(..., alias="SLOParameters")
131
- slo_target: float = Field(..., alias="SLOTarget")
132
- slo_target_unit: str = Field(..., alias="SLOTargetUnit")
133
-
134
-
135
- class SLODocumentV1(ConfiguredBaseModel):
136
- name: str = Field(..., alias="name")
137
- namespaces: list[SLONamespacesV1] = Field(..., alias="namespaces")
138
- slos: Optional[list[SLODocumentSLOV1]] = Field(..., alias="slos")
139
-
140
-
141
89
  class SLODocumentsQueryData(ConfiguredBaseModel):
142
- slo_documents: Optional[list[SLODocumentV1]] = Field(..., alias="slo_documents")
90
+ slo_documents: Optional[list[SLODocument]] = Field(..., alias="slo_documents")
143
91
 
144
92
 
145
93
  def query(query_func: Callable, **kwargs: Any) -> SLODocumentsQueryData: