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.
- {qontract_reconcile-0.10.2.dev167.dist-info → qontract_reconcile-0.10.2.dev169.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.2.dev167.dist-info → qontract_reconcile-0.10.2.dev169.dist-info}/RECORD +13 -10
- reconcile/dashdotdb_slo.py +45 -156
- reconcile/gcp_image_mirror.py +4 -28
- reconcile/gql_definitions/common/saas_files.py +49 -0
- reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +15 -67
- reconcile/gql_definitions/fragments/saas_slo_document.py +82 -0
- reconcile/gql_definitions/introspection.json +249 -229
- reconcile/quay_mirror.py +3 -42
- reconcile/utils/quay_mirror.py +42 -0
- reconcile/utils/slo_document_manager.py +278 -0
- {qontract_reconcile-0.10.2.dev167.dist-info → qontract_reconcile-0.10.2.dev169.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev167.dist-info → qontract_reconcile-0.10.2.dev169.dist-info}/entry_points.txt +0 -0
{qontract_reconcile-0.10.2.dev167.dist-info → qontract_reconcile-0.10.2.dev169.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.2.
|
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
|
{qontract_reconcile-0.10.2.dev167.dist-info → qontract_reconcile-0.10.2.dev169.dist-info}/RECORD
RENAMED
@@ -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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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.
|
808
|
-
qontract_reconcile-0.10.2.
|
809
|
-
qontract_reconcile-0.10.2.
|
810
|
-
qontract_reconcile-0.10.2.
|
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,,
|
reconcile/dashdotdb_slo.py
CHANGED
@@ -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[
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
206
|
-
|
207
|
-
|
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
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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:
|
reconcile/gcp_image_mirror.py
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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.
|
20
|
+
from reconcile.gql_definitions.fragments.saas_slo_document import SLODocument
|
21
21
|
|
22
22
|
|
23
23
|
DEFINITION = """
|
24
|
-
fragment
|
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[
|
90
|
+
slo_documents: Optional[list[SLODocument]] = Field(..., alias="slo_documents")
|
143
91
|
|
144
92
|
|
145
93
|
def query(query_func: Callable, **kwargs: Any) -> SLODocumentsQueryData:
|